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 |
Resources
- How to build an agent for Liveblocks with Chat SDK and AI SDK — Build an AI agent that replies to @-mentions in Liveblocks comment threads with streamed responses and tool calling. Uses Chat SDK, the Liveblocks adapter, AI SDK's ToolLoopAgent, and Redis for thread subscriptions and distributed locking.
See all guides and templates on the resources page.