Events & Callbacks
This feature is only available in paid plans. Learn More
The AI Assistant emits a small set of lifecycle callbacks you can subscribe to from the host app — register them after the editor is initialized using unlayer.addEventListener (alias of registerCallback). Use them to log generations, build per-project dashboards, stream the assistant's output into your own UI, or feed your own analytics pipeline.
Common use case: logging prompts & per-user analytics
The most common reason to wire these callbacks is to track what your users ask the assistant — for product analytics, per-user accounting, support correlation, or content moderation. The recommended pattern:
- 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 and duration. - 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 already carries the embed's attribution context (see Shared context fields), so you can key analytics directly off the event payload — no need to close over your own user object. The end-user id on the events is the one you pass via End-User Identification, which is also what scopes the editor's own per-user state (saved blocks, uploads).
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, // 'template_block' | 'row' | 'content_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';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.
unlayer.addEventListener('ai:assistant:on:request', function (params) {
console.info('AI turn started', {
requestId: params.requestId,
prompt: params.prompt,
dataType: params.dataType, // e.g. 'template_block', 'row', 'content_text'
location: params.location, // { collection, id }
});
});
ai:assistant:on:success
Fires when a turn completes successfully. Carries the wall-clock duration, the detected user locale, and the design payload the assistant produced.
unlayer.addEventListener('ai:assistant:on:success', function (data) {
console.info('AI turn succeeded', {
responseId: data.responseId,
durationMs: data.durationMs,
locale: data.locale, // detected from the user's last message
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. 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 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.
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 every internal stream event the assistant produces during a turn — status text, 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.
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.
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;
}
});