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.
The editor doesn't ship per-user analytics out of the box because only the host app knows who the user is. The callbacks give you the AI side of the story; you bring the identity side. This also makes it straightforward to apply per-user policies (rate-limit a free-tier user, redact sensitive prompts before logging, etc.).
To make sure the editor itself associates per-user state (saved blocks, uploads, etc.) with the same id you use in analytics, identify the end-user to the editor via End-User Identification. The id you pass there is the same one you should put on the AI events below.
unlayer.addEventListener('editor:ready', () => {
unlayer.addEventListener('ai:assistant:on:request', (params) => {
analytics.track('ai_prompt_sent', {
userId: currentUser.id, // your app's user
workspaceId: currentUser.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: currentUser.id,
responseId: data.responseId,
durationMs: data.durationMs,
locale: data.locale,
toolType: data.toolType,
});
});
});
The event reference below documents the full payload of each lifecycle event.
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;
}
});