Skip to main content
Version: 1.402.0

Custom Backend

By default, Unlayer stores collaboration threads and comments for you. You can store them in your own database instead — to own and retain the data, for data residency, compliance, or integration reasons. Just register your own storage and the builder uses it in place of Unlayer's cloud, whether you use Unlayer's cloud or run on-premise.

  • Providers load threads and comments (reads).
  • Callbacks persist changes (writes).

When you register the collaborationThreads provider, the builder calls it instead of Unlayer's cloud storage — likewise for the write callbacks. This works with both Unlayer's cloud and on-premise setups.

unlayer.init({
id: 'editor',
projectId: 1234,
designId: 'design-5678',
user: { id: 76, name: 'Jane Doe' },
features: { collaboration: true },
});
info

When you use Unlayer's cloud, providers and callbacks override the cloud storage when present, and the builder falls back to the cloud when they're absent. In an on-premise setup (with no Unlayer cloud), they're required — the Comments button stays hidden until you register the collaborationThreads provider.


Providers

Register each provider with unlayer.registerProvider(name, handler). The handler receives params and a done function to call with your result.

ProviderParamsExpected result
collaborationThreads{ projectId, designId }{ success: true, data: Thread[] }
collaborationThreadComments{ threadId }{ success: true, data: Comment[] }
unlayer.registerProvider('collaborationThreads', async (params, done) => {
// params = { projectId, designId }
const threads = await fetchThreadsFromYourBackend(params);
done({ success: true, data: threads });
});

unlayer.registerProvider(
'collaborationThreadComments',
async (params, done) => {
// params = { threadId }
const comments = await fetchCommentsFromYourBackend(params.threadId);
done({ success: true, data: comments });
},
);

To make the builder re-fetch threads — for example after your backend receives new comments from another user — call:

unlayer.reloadProvider('collaborationThreads');

Callbacks

Register each callback with unlayer.registerCallback(name, handler). The builder calls them when the user makes a change; your handler persists it and calls done with the saved entity. On failure, call done({ success: false, error }) and the builder will discard the change.

CallbackPayloadExpected result
collaboration:thread:added{ thread } — includes designId, itemId, type, text, user{ success: true, thread } — with server-assigned id, firstComment, timestamps
collaboration:thread:modified{ threadId, data } — e.g. { status: 'resolved' }{ success: true, thread }
collaboration:thread:removed{ threadId }{ success: true }
collaboration:comment:added{ comment } — includes threadId, text, user{ success: true, comment } — with server-assigned id and timestamps
collaboration:comment:modified{ commentId, data, threadId }{ success: true, comment }
collaboration:comment:removed{ commentId, threadId }{ success: true }

Example: creating a thread

unlayer.registerCallback(
'collaboration:thread:added',
async ({ thread }, done) => {
if (!thread?.text || !thread.user?.id) {
done({ success: false, error: new Error('Invalid thread') });
return;
}

// Persist the thread and its first comment, assign IDs and timestamps
const savedThread = await saveThreadToYourBackend(thread);

done({ success: true, thread: savedThread });
},
);

When creating a thread, your backend should also create its first comment from the thread's text and user, and return the thread with firstComment, commentCount, status: 'open', and createdAt / updatedAt set.


Data model

Thread

FieldTypeDescription
idstring | numberUnique thread ID (assigned by your backend).
projectIdstring | numberThe project the design belongs to.
designIdstringThe design the thread is on.
itemIdstringThe ID of the design component the thread is anchored to.
userUserThe user who started the thread.
type'feedback' | 'idea' | 'question' | 'urgent'Thread category.
status'open' | 'resolved'Thread state.
commentCountnumberTotal comments, including the first.
firstCommentCommentThe comment that started the thread.
createdAt / updatedAtstringISO 8601 timestamps.

Comment

FieldTypeDescription
idstring | numberUnique comment ID (assigned by your backend).
threadIdThread idThe thread this comment belongs to.
userUserThe comment author.
textstringThe comment body (plain text).
createdAt / updatedAtstringISO 8601 timestamps.

User

FieldTypeDescription
idstringStable unique user ID.
namestringDisplay name.
avatarstringImage URL; initials are shown when empty.

Tips

  • Sort threads by updatedAt descending and comments by createdAt so the panel shows the latest activity first.
  • Bump the thread's updatedAt when a reply is added so it surfaces at the top of the list.
  • When the last comment of a thread is removed, remove the thread as well.
  • Enforce ownership server-side: only allow users to modify or remove their own threads and comments.