Sign in

Comments

With Liveblocks you can embed a commenting experience into your product, for document reviews, design feedback, video annotations, or discussions on any content. Attach threads to any part of your app, mention users and groups, notify people in-app and by email, and let your backend and AI agents join the conversation.

Comments demo blog

Commenting in various examples

Features

Get started

Choose the features you need. Each guide uses Next.js and can be combined with the others.

Implementation

This is an overview of how each feature can be implemented using our Comments product. Threads live inside rooms, update in realtime for every connected user, and are stored permanently. Pair them with Notifications to reach users who aren’t currently viewing the page.

Commenting

Use useThreads to retrieve each thread in the current room, and render them with the default Thread component. Replies, emoji reactions, editing, and deleting are all built in, and every change appears in realtime for other users. Add a Composer to create new threads.

import { useThreads } from "@liveblocks/react/suspense";import { Composer, Thread } from "@liveblocks/react-ui";
function Comments() { const { threads } = useThreads();
return ( <> {threads.map((thread) => ( <Thread key={thread.id} thread={thread} /> ))} <Composer /> </> );}

The default components are customizable with CSS, and for fully custom interfaces you can combine hooks with primitives. Learn more under Comments.

Contextual comments

Threads become contextual when you store placement data in thread metadata, for example a cell ID in a table, a timestamp in a video, or coordinates on a canvas. Pass metadata when creating a thread, then read it back from each thread to position it in your interface.

import { Composer } from "@liveblocks/react-ui";
function CommentOnCell({ cellId }: { cellId: string }) { // Creates a new thread attached to a table cell return ( <Composer metadata={{ cellId, pinned: true }} /> );}

For canvas-style experiences, a FloatingComposer and CommentPin can create and display threads at any point on the page. Learn more under Metadata.

Mentions and groups

Liveblocks only stores user IDs, so you provide each user’s name and avatar with resolveUsers, and return matching IDs for the @ mention popup with resolveMentionSuggestions. Mentioned users automatically receive an inbox notification.

<LiveblocksProvider  authEndpoint="/api/liveblocks-auth"  resolveUsers={async ({ userIds }) => {    // Return each user's name and avatar from your database    return await (userIds);  }}  resolveMentionSuggestions={async ({ text }) => {    // Return user IDs matching the search text    return await (text);  }}>

You can also mention whole teams at once, such as @everyone or @engineering, by returning group mentions and creating managed groups with Liveblocks.createGroup. Learn more under Users and mentions.

In-app notifications

Mentions and replies create inbox notifications, which are grouped per thread so users aren’t flooded by busy discussions. Render them with useInboxNotifications and the InboxNotification component—these work anywhere in your app, even outside the room.

import { useInboxNotifications } from "@liveblocks/react/suspense";import { InboxNotification, InboxNotificationList } from "@liveblocks/react-ui";
function Inbox() { const { inboxNotifications } = useInboxNotifications();
return ( <InboxNotificationList> {inboxNotifications.map((inboxNotification) => ( <InboxNotification key={inboxNotification.id} inboxNotification={inboxNotification} /> ))} </InboxNotificationList> );}

Show a badge on your inbox icon with useUnreadInboxNotificationsCount. Learn more under Notifications, or explore the Notifications use case for a complete inbox, unread badges, and user settings.

Email notifications

To reach users who are away from your app, enable the notification webhook event in your dashboard. It’s sent per user, batching unread activity together, up to every 30 minutes by default. In your endpoint, prepareThreadNotificationEmailAsReact turns the event into ready-to-render email data.

import { isThreadNotificationEvent } from "@liveblocks/node";import { prepareThreadNotificationEmailAsReact } from "@liveblocks/emails";
// In your webhook endpointif (isThreadNotificationEvent(event)) { const emailData = await prepareThreadNotificationEmailAsReact( liveblocks, event );
if (emailData !== null) { // Render the unread mention or replies, and send with your email provider }}

The same webhook works for Slack, Microsoft Teams, and web push channels. Learn more under Email notifications.

Attachments

The Composer lets users attach files and images to comments by default, uploading and storing them for you, and the Thread component displays them automatically. In custom interfaces, retrieve a presigned file URL with useAttachmentUrl.

import { useAttachmentUrl } from "@liveblocks/react/suspense";
function AttachmentPreview({ attachmentId }: { attachmentId: string }) { const { url } = useAttachmentUrl(attachmentId);
return <img src={url} alt="Comment attachment" />;}

Resolving and filtering

Each thread can be marked as resolved, and the default Thread component includes a resolve button, or you can call useMarkThreadAsResolved yourself. Combine resolved status with metadata in a useThreads query to build filtered views, such as a list of open urgent discussions.

import { useThreads } from "@liveblocks/react/suspense";import { Thread } from "@liveblocks/react-ui";
function OpenUrgentThreads() { const { threads } = useThreads({ query: { resolved: false, metadata: { priority: "urgent" }, }, });
return threads.map((thread) => <Thread key={thread.id} thread={thread} />);}

Server-side commenting

Trusted backend processes can create and modify threads with @liveblocks/node, for example posting status updates from a CI pipeline or importing discussions from another system. Write comment bodies from Markdown with markdownToCommentBody.

import { Liveblocks, markdownToCommentBody } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: process.env.LIVEBLOCKS_SECRET_KEY!,});
await liveblocks.createComment({ roomId: "document-room", threadId: "th_d75sF3...", data: { userId: "deploy-bot", body: markdownToCommentBody("The document was **published** successfully."), },});

Every Comments feature is also available through the REST API.

Agentic commenting

To let AI agents leave feedback, generate the comment text with AI, then use the same server-side APIs shown under Server-side commenting to post it. Give the agent its own user ID, and return its name and avatar from resolveUsers so it appears in threads like any other user.

import { Liveblocks, markdownToCommentBody } from "@liveblocks/node";import { generateText } from "ai";
const liveblocks = new Liveblocks({ secret: process.env.LIVEBLOCKS_SECRET_KEY!,});
const { text } = await generateText({ model: "openai/gpt-5.6-sol", prompt: `Review this paragraph and suggest one improvement: ${paragraph}`,});
await liveblocks.createThread({ roomId: "document-room", data: { comment: { userId: "ai-agent", body: markdownToCommentBody(text), }, metadata: { paragraphId: "paragraph-4" }, },});

Because the thread stores metadata, the AI’s feedback appears contextually in your app, exactly like a human comment. Learn more under agentic users.

Permissions

Comments has its own permission scopes, so you can allow read-only users to still join discussions. For example, viewers of a document can be given comments:write access while keeping the content itself read-only. This can be set when creating or modifying a room, for example with Liveblocks.createRoom.

await liveblocks.createRoom("document-room", {  defaultAccesses: [    // No access by default  ],  groupsAccesses: {    // "viewer" group is read-only, but can comment    viewer: ["*:read", "comments:write"],  },  usersAccesses: {    // "marc@example.com" has full write access    "marc@example.com": ["*:write"],  },});

Threads can also be created with visibility: "private", enabling internal team-only discussions alongside public comments in the same room. Read how to add private commenting, or learn more under Permissions.

Examples

Explore complete examples that combine the features described above.