Skip to main content

Events & Callbacks

Paid Feature

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:

  1. Subscribe to ai:assistant:on:request to capture the user's prompt and the target scope (which row / block / text selection it was scoped to).
  2. Subscribe to ai:assistant:on:success (and :on:error / :on:cancel) to record the outcome and duration.
  3. 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 from unlayer.init({ projectId }); null when none was passed.
  • workspaceId — id of the workspace the project belongs to; may be null on older API deployments.
  • endUserId — id of the end-user identified via unlayer.init({ user }); null when 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'; null when 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

Beta

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;
}
});