Events & Callbacks
This feature is only available in paid plans. Learn More
The AI Assistant emits lifecycle callbacks that you can subscribe to from the
host app. Register them after the editor initializes with
unlayer.addEventListener (an alias of registerCallback). Use them for
prompt telemetry, support diagnostics, product analytics, or mirroring streamed
output in your own UI.
Lifecycle callbacks describe the editor session. Production success callbacks do not include authoritative credit usage, and a client event can be missed or modified. Use the server-side AI Credits API and webhooks for billing, allowance enforcement, and reconciliation.
Lifecycle callbacks are notifications, not authorization hooks. They fire only after the editor has accepted the user action, and the editor does not wait for or interpret their return values. Enforce plan access and per-user limits in your backend, then pass the resulting UI state through the documented feature and credits configuration.
Common use case: logging prompts & per-user analytics
The most common reason to wire these callbacks is to track what users ask the Assistant for product analytics, support correlation, or content moderation:
- Subscribe to
ai:assistant:on:requestto capture the user's prompt and the target scope (which row / block / text selection it was scoped to). - Subscribe to
ai:assistant:on:success(and:on:error/:on:cancel) to record the outcome. Success and error events may includedurationMs; to cover every turn, measure elapsed time from the matching request event. - Forward each event to your analytics pipeline (Mixpanel, Amplitude, Segment, your own warehouse) keyed by the user id you already know in the host app.
Every lifecycle event carries the embed's attribution context (see Shared context fields), so you can key session analytics directly from the payload. The end-user id is the one passed through End-User Identification.
unlayer.addEventListener('editor:ready', () => {
unlayer.addEventListener('ai:assistant:on:request', (params) => {
analytics.track('ai_prompt_sent', {
userId: params.endUserId, // end-user from unlayer.init({ user })
projectId: params.projectId,
workspaceId: params.workspaceId,
requestId: params.requestId,
prompt: params.prompt,
scope: params.dataType, // 'body_block' | 'row_block' | 'text' | ...
target: params.location, // { collection, id }
});
});
unlayer.addEventListener('ai:assistant:on:success', (data) => {
analytics.track('ai_prompt_completed', {
userId: data.endUserId,
responseId: data.responseId,
durationMs: data.durationMs,
locale: data.locale,
toolType: data.toolType,
});
});
});
The event reference below documents the full payload of each lifecycle event.
Shared context fields
Every lifecycle event (on:request, on:success, on:error, on:cancel) carries these attribution fields in addition to its own payload:
projectId— the numeric project id fromunlayer.init({ projectId });nullwhen none was passed.workspaceId— id of the workspace the project belongs to; may benullon older API deployments.endUserId— id of the end-user identified viaunlayer.init({ user });nullwhen no end-user is identified.featureType— classification of the AI feature used this turn:'full_template_gen' | 'block_edit' | 'html_import' | 'image_import' | 'image_generation' | 'image_edit';nullwhen the turn has no classification.
ai:assistant:on:request
Fires at the start of every turn, before any AI call. Use it to time turns, count requests, or capture the prompt and target location.
For Template Importer turns, dataType is import_html or import_image, and prompt contains the optional instructions entered beside the attachment (or an empty string when none were provided). The uploaded HTML or image content is not included in callback payloads or lifecycle logs, but its bytes are sent to Unlayer's AI service and the selected model provider to perform the import.
Registering ai:assistant:generate gives the host application ownership of AI
Assistant generation. The embedded importer and image attachment picker are hidden while that callback is registered, because its single-result contract does not represent these workflows. Unlayer's default importer and conversational image operations are not invoked for host-owned turns.
When an import would replace a non-empty design, the turn does not start until the end-user clicks Replace design. Choosing Cancel at that confirmation is a local UI decision: it emits no on:request event and, because no lifecycle started, no on:success, on:error, or on:cancel event.
unlayer.addEventListener('ai:assistant:on:request', function (params) {
console.info('AI turn started', {
requestId: params.requestId,
prompt: params.prompt,
dataType: params.dataType, // e.g. 'body_block', 'row_block', 'text'
location: params.location, // { collection, id }
});
});
ai:assistant:on:success
Fires when a turn completes successfully. Carries the chat locale supplied by the editor and the design payload the assistant produced. Streaming Assistant turns also include the wall-clock duration as durationMs; Template Importer turns omit it, so time those from the matching on:request event.
In local development, dev, and QA environments, completion callbacks also include a diagnostic usage object with token counts, estimatedCostMicroUsd, and aiCreditsUsed. Usage diagnostics are intentionally omitted from these host callbacks in staging and production. For a verified edit blocked entirely by template permissions, usage.aiCreditsUsed is 0 when the normal aggregate charge is at most 200 credits, even though diagnostic token counts and provider cost may be nonzero. Larger protected no-change turns and partial edits use credits normally. See Pricing.
unlayer.addEventListener('ai:assistant:on:success', function (data) {
console.info('AI turn succeeded', {
responseId: data.responseId,
durationMs: data.durationMs,
locale: data.locale, // current AI chat locale
toolType: data.toolType,
});
});
ai:assistant:on:error
Fires when a turn fails — after the editor has already retried internally. Includes the error name and HTTP status code if applicable. Structured service failures may also include code, limitKey, remaining, total, upgrade_url, and canClaimStarterCredits. Cancellations route to ai:assistant:on:cancel instead — they do not fire here.
unlayer.addEventListener('ai:assistant:on:error', function (data) {
console.warn('AI turn failed', {
name: data.name,
message: data.message,
statusCode: data.statusCode,
durationMs: data.durationMs,
});
});
ai:assistant:on:cancel
Fires when a turn that already emitted ai:assistant:on:request is interrupted before completion — the user clicked Stop, started a new request, removed the target element, or the request timed out. The reason discriminates the cause. It does not fire when an end-user declines the Template Importer replacement confirmation because that choice happens before a request starts.
The lifecycle guarantee begins with ai:assistant:on:request: after that event, an interrupted turn emits ai:assistant:on:cancel. Actions taken before a request starts, including declining replacement confirmation, are outside the lifecycle and emit none of these callbacks.
For Template Importer turns, stop_button is reserved for the explicit Stop action. Target changes, target removal, and Assistant teardown use item_removed.
unlayer.addEventListener('ai:assistant:on:cancel', function (data) {
console.info('AI turn cancelled', {
reason: data.reason, // 'stop_button' | 'new_request' | 'item_removed' | 'timeout'
});
});
ai:assistant:on:stream
ai:assistant:on:stream is in beta. The set of event.type values, the shape of each payload, and the firing semantics may change as the streaming pipeline evolves.
Fires for the supported stream events the assistant produces during a turn — optional status text for long generations, partial text deltas, follow-up suggestions, and design operations as they arrive. Useful for mirroring the assistant's output into your own UI, building progress indicators, or debugging.
Image results are currently rendered by the built-in chat and are not forwarded as image-result events through this callback. Use the conversational Images API when building a separate image chat UI. Image references are sent to the image provider for the requested operation; lifecycle callbacks are not a file-upload API.
The payload is a discriminated union — switch on event.type to handle each kind:
unlayer.addEventListener('ai:assistant:on:stream', function (event) {
switch (event.type) {
case 'start':
// A new assistant turn began.
console.info('AI stream started', {
toolType: event.toolType,
messageId: event.messageId,
});
break;
case 'status':
// Human-readable progress label (e.g. "Analyzing your request…").
console.info('AI status:', event.text);
break;
case 'text-delta':
// Incremental text chunk for the chat bubble identified by blockId.
appendToBubble(event.blockId, event.delta);
break;
case 'suggest-options':
// Quick-reply suggestions the assistant proposes for the user's next turn.
if (event.question) renderQuestion(event.question);
renderQuickReplies(event.options); // [{ label, prompt }, ...]
break;
case 'design-partial':
// Snapshot of the design after a structural update.
console.info('Design snapshot', event.full);
break;
case 'design-operation':
// A single design mutation (add/remove/move/update) about to be applied.
console.info('Design op', event.op);
break;
}
});
Protected designs
Generation callbacks do not bypass template permissions. In live mode, the editor checks the callback result against the current design before applying it. Locked content and disabled properties remain unchanged. Return the original element IDs for existing blocks, including protected blocks, so the editor can match them reliably. Edit mode allows the same unrestricted editing access as the administrator.