Widget Documentation
Everything you need to embed and customize the <image-layer> widget for image, video, voice, and text workflows in your application.
Quick Start
Add the widget to any HTML page after your backend creates an end-user session token:
<script type="module" src="https://cdn.imagelayer.app/widget/latest/image-layer.js"></script>
<image-layer id="imagelayer" api-url="https://api.imagelayer.app"></image-layer>
<script>
const widget = document.getElementById('imagelayer');
widget.apiKey = 'il_pub_your_publishable_key_here';
widget.sessionToken = 'SESSION_TOKEN_FROM_YOUR_SERVER';
</script>The widget renders as a self-contained UI. Users choose a supported workflow, generate on-brand output, and stay inside your product the whole time. Keep live API keys on trusted servers where possible; use the session token to scope each end user.
Installation
CDN rolling path
Use latest for demos, previews, and quick starts. The bytes can change as new widget releases roll out.
<script type="module" src="https://cdn.imagelayer.app/widget/latest/image-layer.js"></script>Pinned CDN path (recommended for production)
Pin a semantic version or full sha-* path and include Subresource Integrity when you need reproducible rollbacks or stricter supply-chain controls. Existing pinned paths are byte-immutable; publication fails rather than replacing them.
<script
type="module"
src="https://cdn.imagelayer.app/widget/<version>/image-layer.js"
integrity="sha384-<hash>"
crossorigin="anonymous"
></script>Self-hosted
Copy the built widget files to your static assets and reference the local path:
<script type="module" src="/assets/image-layer.js"></script>Attributes
All configuration is done via HTML attributes on the <image-layer> element.
| Attribute | Type | Default | Description |
|---|---|---|---|
| apiKey | property string | "" | Publishable widget key assigned as a JavaScript property. Do not place secret keys in HTML attributes. |
| api-url | string | "" | Required. Base URL of the ImageLayer API. |
| sessionToken | property string | "" | JWT from POST /v1/auth/session. Assign it as a JavaScript property for live generation flows. |
| authToken | property string | "" | Organization auth token used by dashboard and playground flows. Use together with sessionToken. |
| theme | "light" | "dark" | "auto" | "auto" | Color scheme. "auto" follows the system light/dark preference; "dark" forces dark palette. |
| show-model-selector | boolean | true | Show the model dropdown. Because the default is true, set the JavaScript property to false to disable it reliably; HTML attribute presence means true. |
| demo-mode | boolean | false | Enable the limited hosted demo experience. As with every HTML boolean attribute, demo-mode="false" is still true; remove it to disable demo mode. |
| demo-config | string (JSON) | undefined | JSON configuration for demo mode presets, visible modes, locked-mode tooltips, and interactive preview behavior. |
| brand-guidelines | boolean | false | Enables the brand guidelines toggle. Attribute presence means true, including brand-guidelines="false". |
| brand-guidelines-data | string (JSON) | undefined | JSON object with brand guidelines configuration (colors, tone, style rules). |
| animation-style | "spring" | "smooth" | "none" | "spring" | Controls the animation style for UI transitions. |
demo-config also supports preview-specific keys such asvisible_output_modes,disabled_output_modes_reason,preview_behavior,preview_gate_message,preview_cta_href,preview_cta_label, andhistory_preview_items.
Events
The widget emits custom events that bubble up through the DOM. Listen with addEventListener.
| Event | Fires when | event.detail |
|---|---|---|
| il:generate | User submits a prompt or selects a history item. | { prompt: string, output_mode: string, content_type?: string, seed?: number } |
| il:image-generated | Image generation completes before optional branding. | { generation_id: string, url: string, seed: number | null, prompt_metadata: object | null } |
| il:submit | User finalizes the branded image in the image flow. | { url: string, blob: Blob, generation_id?: string, seed?: number, prompt_metadata?: object } |
| il:video-generated | Video generation completes. | { url: string, duration?: number } |
| il:audio-generated | Audio generation completes. | { url: string, voice?: string, duration?: number, format?: string } |
| il:voiceover-generated | A video voiceover finishes. | { id: string, video_url: string, audio_url?: string, voice?: string, duration?: number } |
| il:verification-required | The hosted demo needs an external verification step. | { prompt: string, output_mode: string } |
| il:error | An error occurs during generation. | { error: string, error_code?: string } |
| il:text-generated | AI text generation completes. | { text: string } |
| il:quota-exceeded | The user exceeds their credit quota. | { current: number, limit: number, plan: string, canPurchaseCredits: boolean } |
| il:campaign-start | A Campaign bundle is created and queued. | { campaignId, presetId, status, source, goal, channels, estimatedCredits, hostMetadata? } |
| il:campaign-generated | A Campaign reaches a usable ready or partial state. | { campaignId, presetId, status, items, completedItemCount, failedItemCount, hostMetadata? } |
| il:campaign-partial-failure | Some Campaign items fail while usable items remain. | { campaignId, presetId, status: 'partially_failed', items, failedItems, completedItemCount, failedItemCount } |
| il:campaign-submit | The user submits selected Campaign assets. | { campaignId, presetId, status: 'submitted', selectedAssets, submittedAt, hostMetadata? } |
| il:campaign-error | A non-quota Campaign error occurs. | { campaignId, presetId, status, error: { code, message, itemId?, retryable? } } |
| il:campaign-quota-exceeded | Campaign creation is blocked by credits or quota. | { campaignId, presetId, status, estimatedCredits, availableCredits, error } |
const widget = document.querySelector('image-layer');
widget.addEventListener('il:generate', (e) => {
console.log('Generating:', e.detail.prompt);
});
widget.addEventListener('il:submit', (e) => {
console.log('Final image URL:', e.detail.url);
// e.detail.blob contains the raw Blob for upload
});
widget.addEventListener('il:video-generated', (e) => {
console.log('Video ready:', e.detail.url);
});
widget.addEventListener('il:audio-generated', (e) => {
console.log('Audio ready:', e.detail.url);
});
widget.addEventListener('il:error', (e) => {
console.error('Widget error:', e.detail.error);
});Theming
The widget uses CSS custom properties for full visual customization. Override them on the <image-layer> element to match your brand. Pair every --il-primary override with an--il-action-foreground value that keeps normal-size action labels at 4.5:1 contrast or better. API-provided organization customization derives this pair automatically.
image-layer {
/* Brand colors */
--il-primary: #5558DC;
--il-primary-hover: #4447C6;
--il-action-foreground: #FFFFFF;
--il-accent: #10B981;
--il-accent-hover: #059669;
/* Backgrounds */
--il-bg: #FFFFFF;
--il-bg-secondary: #F8FAFC;
--il-bg-tertiary: #F1F5F9;
/* Text */
--il-text: #0F172A;
--il-text-secondary: #475569;
--il-text-muted: #94A3B8;
/* Borders */
--il-border: #E2E8F0;
--il-border-hover: #CBD5E1;
/* Error / Success */
--il-error: #EF4444;
--il-success: var(--il-accent);
/* Border radius */
--il-radius: 10px;
--il-radius-sm: 6px;
--il-radius-lg: 14px;
--il-radius-full: 9999px;
/* Typography */
--il-font: 'Inter', system-ui, sans-serif;
/* Shadows */
--il-shadow-xs: 0 1px 2px rgba(0,0,0,0.04);
--il-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px -1px rgba(0,0,0,0.08);
--il-shadow-md: 0 4px 6px -1px rgba(0,0,0,0.08), 0 2px 4px -2px rgba(0,0,0,0.06);
--il-shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.08), 0 4px 6px -4px rgba(0,0,0,0.04);
}Stable CSS parts
For narrow element-level overrides, the root element exportsprompt-input,generate-button,preview,preview-image,preview-video, andpreview-audio through nested shadow roots. Prefer custom properties for theme-wide changes and ::part(...) for isolated overrides.
Dark Theme
Set theme="dark" on the element. Override dark-specific variables:
image-layer[theme="dark"] {
--il-bg: #0F172A;
--il-text: #F1F5F9;
--il-border: #334155;
--il-primary: #818CF8;
--il-action-foreground: #0B0F1A;
}Server-Side Setup
For live embeds, the widget requires a session token to authenticate end users. Your server obtains this token by calling the ImageLayer API. Dashboard and playground flows also pass an auth-token.
1. Get your API key
Go to Dashboard → API Keys and create a secret key for your backend. Use a publishable key in the browser and add every site origin that will embed the widget.
2. Create a session (server-side)
Call POST /v1/auth/session from your backend:
// Run on your backend, not in the browser.
const body = JSON.stringify({
external_user_id: 'user-123',
display_name: 'Jane Doe',
});
await fetch('https://api.imagelayer.app/v1/auth/session', {
method: 'POST',
headers: {
'X-API-Key': process.env.IMAGELAYER_SECRET_KEY,
'Content-Type': 'application/json',
},
body,
});Response:
{
"session_token": "eyJhbG...",
"expires_at": "2026-03-29T22:00:00.000Z",
"user": {
"id": "uuid",
"external_id": "user-123",
"display_name": "Jane Doe"
}
}3. Pass the token to the widget
Assign session_token and the publishable key to the widget as JavaScript properties after fetching them from your API.
Full Integration Example
A complete HTML page with session creation and widget embedding.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My App — AI Media Generator</title>
<script type="module" src="https://cdn.imagelayer.app/widget/latest/image-layer.js"></script>
<style>
image-layer {
max-width: 480px;
margin: 40px auto;
--il-primary: #2563EB;
--il-radius-lg: 16px;
}
</style>
</head>
<body>
<image-layer
id="widget"
api-url="https://api.imagelayer.app"
theme="light"
></image-layer>
<script>
// Fetch session token from YOUR backend
async function initWidget() {
const res = await fetch('/api/imagelayer-session');
const { session_token, publishable_key } = await res.json();
const widget = document.getElementById('widget');
widget.apiKey = publishable_key;
widget.sessionToken = session_token;
}
const widget = document.getElementById('widget');
widget.addEventListener('il:submit', (e) => {
// Upload the final branded image to your storage
const formData = new FormData();
formData.append('image', e.detail.blob, 'branded-image.png');
fetch('/api/upload', { method: 'POST', body: formData });
});
initWidget();
</script>
</body>
</html>Demo Mode
For previews and local development, use demo mode to render the hosted limited demo experience:
<image-layer
api-url="https://api.imagelayer.app"
demo-mode
show-model-selector
theme="dark"
></image-layer>