Liveblocks
Chat SDK adapter backed by Liveblocks Comments. Maps Liveblocks rooms, threads, and comments to the Chat SDK Channel/Thread/Message model.
Install
pnpm add @liveblocks/chat-sdk-adapterQuick start
import { Chat } from "chat";
import { createMemoryState } from "@chat-adapter/state-memory";
import {
createLiveblocksAdapter,
type LiveblocksAdapter,
} from "@liveblocks/chat-sdk-adapter";
const bot = new Chat<{ liveblocks: LiveblocksAdapter }>({
userName: "MyBot",
adapters: {
liveblocks: createLiveblocksAdapter({
apiKey: process.env.LIVEBLOCKS_SECRET_KEY,
webhookSecret: process.env.LIVEBLOCKS_WEBHOOK_SECRET,
botUserId: "my-bot-user",
botUserName: "MyBot",
}),
},
state: createMemoryState(),
});
bot.onNewMention(async (thread, message) => {
await thread.adapter.addReaction(thread.id, message.id, "👀");
await thread.post(`Hello, ${message.author.userName}!`);
});The adapter maps Liveblocks rooms to Chat SDK channels, comment threads to Chat SDK threads, and individual comments to messages — so the rest of the Chat SDK API (subscriptions, handlers, posts, reactions) works exactly the same as with any other adapter.
Configuration
Prop
Type
Resolver return types follow the @liveblocks/core user and group metadata shapes (U["info"], DGI).
Resolving mentions
When comments contain @mentions, supply resolveUsers (and optionally resolveGroupsInfo):
const adapter = createLiveblocksAdapter({
apiKey: process.env.LIVEBLOCKS_SECRET_KEY,
webhookSecret: process.env.LIVEBLOCKS_WEBHOOK_SECRET,
botUserId: "my-bot-user",
resolveUsers: async ({ userIds }) => {
const users = await getUsersFromDatabase(userIds);
return users.map((user) => ({
name: user.fullName,
avatar: user.avatarUrl,
}));
},
resolveGroupsInfo: async ({ groupIds }) => {
const groups = await getGroupsFromDatabase(groupIds);
return groups.map((group) => ({ name: group.displayName }));
},
});Platform setup
- Create a Liveblocks project with rooms using Comments.
- In the dashboard, copy a secret key (
sk_...) for server-side REST API calls. - Create a webhook signing secret (
whsec_...) and configure webhooks to subscribe to:commentCreatedcommentReactionAddedcommentReactionRemoved
- Choose a stable
botUserIdconsistent with how your app identifies users — either a real user ID in your system or a dedicated bot ID you issue.
Point your Liveblocks webhook URL at the route that forwards requests to bot.webhooks.liveblocks (see Webhook events).
Webhook events
Supported Liveblocks webhook types:
| Event | Role |
|---|---|
commentCreated | Drives Chat SDK message processing |
commentReactionAdded | Drives reaction handlers |
commentReactionRemoved | Drives reaction handlers |
import { after } from "next/server";
import { bot } from "@/lib/bot";
export async function POST(request: Request) {
return bot.webhooks.liveblocks(request, {
waitUntil: (task) => after(() => task),
});
}The adapter verifies signatures with webhookSecret; invalid requests return 401. Passing waitUntil lets work continue after the response is sent in serverless environments like Vercel.
ID encoding
- Thread ID:
liveblocks:{roomId}:{threadId} - Channel ID:
liveblocks:{roomId}
encodeThreadId
adapter.encodeThreadId(data: { roomId: string; threadId: string }): string;const encoded = adapter.encodeThreadId({
roomId: "my-room",
threadId: "th_abc123",
});
// "liveblocks:my-room:th_abc123"decodeThreadId
adapter.decodeThreadId(threadId: string): { roomId: string; threadId: string };Throws if the format is invalid. Room IDs may contain : — the last : separates the threadId, so Liveblocks thread IDs themselves must not contain :.
Message format
Liveblocks Comments use a simpler body model than full Markdown. Outbound content from the Chat SDK is converted automatically and some structure is flattened.
Supported: paragraphs with bold, italic, code, strikethrough, links, and @mentions (users and groups).
Flattened to plain text / paragraphs: headings, bullet and numbered lists, code blocks, tables (rendered as ASCII inside a paragraph), raw HTML. Card payloads become markdown/plain text (or fallbackText); interactivity is not preserved.
Reactions
Use Unicode emoji directly, or names like thumbs_up which normalize to emoji where supported. Unknown custom IDs can fail API validation:
await thread.adapter.addReaction(thread.id, message.id, "👍");
await thread.adapter.addReaction(thread.id, message.id, "thumbs_up");Examples
- Liveblocks Chat SDK Bot — Next.js bot that responds to @mentions and reactions in Liveblocks comment threads (source).
- Liveblocks Chat SDK AI Bot — same stack with an AI-powered reply flow (source).
- Full walkthrough: Get started with a Chat SDK bot using Liveblocks and Next.js.
Feature support
Messaging
| Feature | Supported |
|---|---|
| Post message | |
| Edit message | |
| Delete message | |
| File uploads | Attachments |
| Streaming | |
| Scheduled messages |
Rich content
| Feature | Supported |
|---|---|
| Card format | Flattened to text |
| Buttons | |
| Link buttons | |
| Select menus | |
| Tables | Flattened to text |
| Fields | |
| Images in cards | |
| Modals |
Conversations
| Feature | Supported |
|---|---|
| Slash commands | |
| Mentions | Users + groups |
| Add reactions | Unicode emoji |
| Remove reactions | Unicode emoji |
| Typing indicator | |
| DMs | |
| Ephemeral messages | |
| User lookup | resolveUsers |
| Parent subject | |
| Native client | |
| Custom API endpoint |
Message history
| Feature | Supported |
|---|---|
| Fetch messages | |
| Fetch single message | |
| Fetch thread info | |
| Fetch channel messages | |
| List threads | |
| Fetch channel info | |
| Post channel message |