Sign in

@liveblocks/node

@liveblocks/node provides you with Node.js APIs for authenticating Liveblocks users and for implementing webhook handlers. This library is only intended for use in your Node.js back end.

Liveblocks client

The Liveblocks client offers access to our REST API.

import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: "",});

Authentication

To authenticate your users with Liveblocks, you have the choice between two different APIs.

Liveblocks.identifyUser

Creates an ID token that is used to authenticate a user in your application. This is a wrapper around the Get ID Token API and returns the same response.

const { body, status } = await liveblocks.identifyUser({  // Required, the current user's ID  userId: "marie@example.com",});

A number of options are also available, enabling you to set up permissions and user metadata.

const { body, status } = await liveblocks.identifyUser(  {    // Required, the current user's ID    userId: "marie@example.com",
// Optional, only view resources on this organization organizationId: "acme-corp",
// Optional, used to provision room access on group level groupIds: ["marketing", "engineering"], }, { // Optional, custom user metadata userInfo: { name: "Marie", color: "#00ff00", avatar: "https://example.com/avatar/marie.jpg", }, });
Don't cache tokens

Never cache your access token authentication endpoint, as your client will not function correctly. The Liveblocks client will cache results for you, only making requests to the endpoint if necessary, such as when the token has expired.

Granting ID token permissions

You can pass additional options to identifyUser, enabling you to create complex workspace permissions and room permissions. For example, this user can only see resources in the acme-corp workspace, and they’re part of a marketing rooms group within it.

const { body, status } = await liveblocks.identifyUser({  // Required, the current user's ID  userId: "marie@example.com",
// Optional, only view resources on this workspace organizationId: "acme-corp",
// Optional, used to provision room access on group level groupIds: ["marketing"],});

Learn more about ID token permissions.

Text editor user data

When using text editor integrations, user data is inserted into their live cursor within the editor, showing their name and color. This data originates from the userInfo property.

const { body, status } = await liveblocks.identifyUser(  {    // Required, the current user's ID    userId: "marie@example.com",  },  {    // Optional, custom user metadata    userInfo: {      // Used in text editor live carets      name: "Marie",      color: "#00ff00",    },  });
Custom user metadata

You can pass additional options to prepareSession, enabling you to add custom user metadata to the session. This metadata can be accessed by all users in the room, and is useful for building features such as live avatar stacks.

const { body, status } = await liveblocks.identifyUser(  {    // Required, the current user's ID    userId: "marie@example.com",  },  {    // Optional, custom user metadata    userInfo: {      // Add custom properties to use on front end, e.g. avatar stacks      avatar: "https://example.com/avatar/marie.jpg",      // ...    },  });

To access it on the front end, use hooks such as useSelf and useOthers.

const currentUser = useSelf();
// "https://example.com/avatar/marie.jpg"console.log(currentUser.info.avatar);
How ID tokens work

The purpose of this API is to help you implement your custom authentication back end (i.e. the server part of the diagram). You use the liveblocks.identifyUser() API if you’d like to issue ID tokens from your back end. An ID token does not grant any permissions in the token directly. Instead, it only securely identifies your user, and then uses any permissions set via the Permissions REST API to decide whether to allow the user on a room-by-room basis.

Use this approach if you’d like Liveblocks to be the source of truth for your user’s permissions.

What are ID tokens?

Issuing identity tokens is like issuing membership cards. Anyone with a membership card can try to enter a room, but your permissions will be checked at the door. The Liveblocks servers perform this authorization, so your permissions need to be set up front using the Liveblocks REST API.

Auth diagram

Implement your back end endpoint as follows:

const { body, status } = await liveblocks.identifyUser(  {    userId: "marie@example.com", // Required, user ID from your DB    groupIds: ["marketing", "engineering"],    // Optional, identify the user in a specific organization    organizationId: "acme-corp",  },
// Optional { userInfo: { name: "Marie", avatar: "https://example.com/avatar/marie.jpg", }, });
return new Response(body, { status });

userId (required) is a string identifier to uniquely identify your user with Liveblocks. This value will be used when counting unique MAUs in your Liveblocks dashboard. You can refer to these user IDs in the Permissions REST API when assigning group permissions.

groupIds (optional) can be used to specify which groups this user belongs to. These are arbitrary identifiers that make sense to your app, and that you can refer to in the Permissions REST API when assigning group permissions.

organizationId (optional) is the organization for this user, will be set to default if not provided.

userInfo (optional) is any custom JSON value, which you can use to attach static metadata to this user’s session. This will be publicly visible to all other people in the room. Useful for metadata like the user’s full name, or their avatar URL.

ID tokens example

Here’s a real-world example of ID tokens in a Next.js route handler/endpoint. You can find examples for other frameworks in our authentication section.

Next.js
import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: "",});
export default async function auth(req, res) { /** * Implement your own security here. * * It's your responsibility to ensure that the caller of this endpoint * is a valid user by validating the cookies or authentication headers * and that it has access to the requested room. */
// Get the current user from your database const user = (req);
// Create an ID token for the user const { body, status } = await liveblocks.identifyUser( { userId: user.id, }, { userInfo: { name: user.fullName, color: user.favoriteColor, }, } );
return new Response(body, { status });}

Liveblocks.prepareSession

Creates an access token that is used to authenticate a user in your application. This is a wrapper around the Get Access Token API and returns the same response.

const session = liveblocks.prepareSession(  // Required, the current user's ID  "marie@example.com");

A number of options are also available, enabling you to set up permissions and user metadata.

const session = liveblocks.prepareSession(  // Required, the current user's ID  "marie@example.com",  {    // Optional, only view resources on this organization    organizationId: "acme-corp",
// Optional, used to provision room access on group level groupIds: ["marketing"],
// Optional, custom user metadata userInfo: { name: "Marie", color: "#00ff00", avatar: "https://example.com/avatar/marie.jpg", }, });
Don't cache tokens

Never cache your access token authentication endpoint, as your client will not function correctly. The Liveblocks client will cache results for you, only making requests to the endpoint if necessary, such as when the token has expired.

Granting access token permissions

Using session.allow(), you can grant full or read-only permissions to the user to select rooms. Wildcards can be used to enable granting permissions to multiple rooms at once using naming patterns.

const session = liveblocks.prepareSession(  // Required, the current user's ID  "marie@example.com");
// Giving access to an individual roomssession.allow("room-id-1", ["*:write"]);
// Giving read-only access to this roomsession.allow("room-id-2", ["*:read"]);
// Giving access to multiple rooms with a wildcard// `design-room-1`, `design-room-2`, etc.session.allow("design-room:*", ["*:write"]);

Learn more about access token permissions.

Additionally, you can pass additional options to prepareSession, enabling you to create complex permissions using organizations and accesses. For example, this user can only see resources in the acme-corp organization, and they're part of a marketing group within it.

const session = liveblocks.prepareSession(  // Required, the current user's ID  "marie@example.com",  {    // Optional, only view resources on this organization    organizationId: "acme-corp",
// Optional, used to provision room access on group level groupIds: ["marketing"], });
Text editor user data

When using text editor integrations, user data is inserted into their live cursor within the editor, showing their name and color. This data originates from the userInfo property of the session.

const session = liveblocks.prepareSession(  // Required, the current user's ID  "marie@example.com",  {    // Optional, user metadata    userInfo: {      // Used in text editor live carets      name: "Marie",      color: "#00ff00",    },  });
Custom user metadata

You can pass additional options to prepareSession, enabling you to add custom user metadata to the session. This metadata can be accessed by all users in the room, and is useful for building features such as live avatar stacks.

const session = liveblocks.prepareSession(  // Required, the current user's ID  "marie@example.com",  {    // Optional, custom user metadata    userInfo: {      // Add custom properties to use on front end, e.g. avatar stacks      avatar: "https://example.com/avatar/marie.jpg",      // ...    },  });

To access it on the front end, use hooks such as useSelf and useOthers.

const currentUser = useSelf();
// "https://example.com/avatar/marie.jpg"console.log(currentUser.info.avatar);
How access tokens work

The purpose of this API is to help you implement your custom authentication back end (i.e. the server part of the diagram). You use the liveblocks.prepareSession() API if you’d like to issue access tokens from your back end.

What are access tokens?

Issuing access tokens is like issuing hotel key cards from a hotel’s front desk (your back end). Any client with a key card can enter any room that the card gives access to. It’s easy to give out those key cards right from your back end.

Auth diagram

To implement your back end, follow these steps:

  1. Create a session

    const session = liveblocks.prepareSession(  "marie@example.com",   // Required, user ID from your DB  {    // Optional, custom static metadata for the session    userInfo: {      name: "Marie",      avatar: "https://example.com/avatar/marie.jpg",    },    // Optional, authenticate this user on a specific organization    organizationId: "acme-corp",  });

    The userId (required) is an identifier to uniquely identifies your user with Liveblocks. This value will be used when counting unique MAUs in your Liveblocks dashboard.

    The userInfo (optional) is any custom JSON value, which can be attached to static metadata to this user’s session. This will be publicly visible to all other people in the room. Useful for metadata like the user’s full name, or their avatar URL.

    The organizationId (optional) is the organization for this session, will be set to default if not provided.

  2. Decide which permissions to allow this session

    session.allow("my-room-1", ["*:write"]);session.allow("my-room-2", ["*:write"]);session.allow("my-room-3", ["*:write"]);session.allow("my-team:*", ["*:read"]);
    Be diligent

    You’re specifying what’s going to be allowed so be careful what permissions you’re giving your users. You’re responsible for this part.

  3. Authorize the session

    Finally, authorize the session. This step makes the HTTP call to the Liveblocks servers. Liveblocks will return a signed access token that you can return to your client.

    // Requests the Liveblocks servers to authorize this sessionconst { body, status } = await session.authorize();return new Response(body, { status });
Access tokens example

Here’s a real-world example of access tokens in a Next.js route handler/endpoint. You can find examples for other frameworks in our authentication section.

route.ts
import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: "",});
export async function POST(request: Request) { /** * Implement your own security here. * * It's your responsibility to ensure that the caller of this endpoint * is a valid user by validating the cookies or authentication headers * and that it has access to the requested room. */
// Get the current user from your database const user = (request);
// Start an auth session inside your endpoint const session = liveblocks.prepareSession( user.id, { userInfo: user.metadata } // Optional );
// Implement your own security, and give the user access to the room const { room } = await request.json(); if (room && (user, room)) { session.allow(room, ["*:write"]); }
// Retrieve a token from the Liveblocks servers and pass it to the // requesting client const { body, status } = await session.authorize(); return new Response(body, { status });}

Room

Liveblocks.getRooms

Returns a list of rooms that are in the current project. The project is determined by the secret key you’re using. Rooms are sorted by creation time, with the newest room at index 0. This is a wrapper around the Get Rooms API and returns the same response.

const { data: rooms, nextCursor } = await liveblocks.getRooms();
// A list of rooms// [{ type: "room", id: "my-room-id", ... }, ...]console.log(rooms);
// A pagination cursor used for retrieving the next page of results with `startingAfter`// "L3YyL3Jvb21z..."console.log(nextCursor);

A number of options are also available, enabling you to filter for certain rooms.

const { data: rooms, nextCursor } = await liveblocks.getRooms({  // Optional, the amount of rooms to load, between 1 and 100, defaults to 20  limit: 20,
// Optional, filter for rooms that allow entry to group ID(s) in `groupsAccesses` groupIds: ["engineering", "design"],
// Optional, filter for rooms that allow entry to a user's ID in `usersAccesses` userId: "my-user-id",
// Optional, use advanced filtering query: { // Optional, filter for rooms with an ID that starts with specific string roomId: { startsWith: "liveblocks:", }, // Optional, filter for rooms with custom metadata in `metadata` metadata: { roomType: "whiteboard", }, },
// Optional, authenticate this user on a specific organization organizationId: "my-organization-id",
// Optional, cursor used for pagination, use `nextCursor` from the previous page's response startingAfter: "L3YyL3Jvb21z...",});

The query option also allows you to pass a query language string instead of a query object.

Pagination

You can use nextCursor to paginate rooms. In this example, when getNextPage is called, the next set of rooms is added to pages.

import { RoomData } from "@liveblocks/node";
// An array of pages, each containing a list of retrieved roomsconst pages: RoomData[][] = [];
// Holds the pagination cursor for the next set of roomslet startingAfter;
// Call to get the next page of roomsasync function getNextPage() { const { data, nextCursor } = await liveblocks.getRooms({ startingAfter }); pages.push(data); startingAfter = nextCursor;}

If you’d like to iterate over all your rooms, it’s most convenient to use liveblocks.iterRooms instead. This method automatically paginates your API requests.

Liveblocks.iterRooms

Works similarly to liveblocks.getRooms, but instead returns an asynchronous iterator, which helps you iterate over all selected rooms in your project, without having to manually paginate through the results.

const roomsIterator = liveblocks.iterRooms();
for await (const room of roomsIterator) { // { type: "room", id: "my-room-id", metadata: {...}, ... } console.log(room);}

A number of options are also available, enabling you to filter for certain rooms.

const roomsIterator = await liveblocks.iterRooms({  // Optional, filter for rooms that allow entry to group ID(s) in `groupsAccesses`  groupIds: ["engineering", "design"],
// Optional, filter for rooms that allow entry to a user's ID in `usersAccesses` userId: "my-user-id",
// Optional, use advanced filtering query: { // Optional, filter for rooms with an ID that starts with specific string roomId: { startsWith: "liveblocks:", }, // Optional, filter for rooms with custom metadata in `metadata` metadata: { roomType: "whiteboard", }, },});
for await (const room of roomsIterator) { // { type: "room", id: "my-room-id", metadata: {...}, ... } console.log(room);}

The query option also allows you to pass a query language string instead of a query object.

Mass deleting rooms

You can use iterRooms to efficiently delete multiple rooms at once. This example shows how to delete rooms in batches of 50 concurrent deletions at a time:

const MAX_CONCURRENT = 50;const queue: Promise<void>[] = [];
for await (const room of liveblocks.iterRooms({ // Optionally filter for certain rooms // ...})) { if (queue.length >= MAX_CONCURRENT) { await Promise.race(queue); }
const promise = liveblocks .deleteRoom(room.id) .finally(() => queue.splice(queue.indexOf(promise), 1));
queue.push(promise);}
await Promise.all(queue);

This approach is useful when you need to delete a large number of rooms, as it automatically handles pagination and allows you to control the concurrency of deletions. You can use any of the filtering options shown above to select which rooms to delete.

Liveblocks.createRoom

Programmatically creates a new room from a room ID. The defaultAccesses option is required. Setting defaultAccesses to ["*:write"] creates a public room, whereas setting it to [] will create a private room that needs ID token permission to enter. This is a wrapper around the Create Room API and returns the same response.

const room = await liveblocks.createRoom("my-room-id", {  defaultAccesses: ["*:write"],});
// { type: "room", id: "my-room-id", metadata: {...}, ... }console.log(room);

A number of room creation options are available, allowing you to set permissions and attach custom metadata.

const room = await liveblocks.createRoom("my-room-id", {  // The default room permissions. `[]` for private, `["*:write"]` for public.  defaultAccesses: [],
// Optional, the room's group ID permissions groupsAccesses: { design: ["*:write"], engineering: ["*:read"], },
// Optional, the room's user ID permissions usersAccesses: { "my-user-id": ["*:write"], },
// Optional, custom metadata to attach to the room metadata: { myRoomType: "whiteboard", },
// Optional, create it on a specific organization organizationId: "acme-corp",});

Group and user permissions are only used with ID token authorization, learn more about managing permission with ID tokens.

Liveblocks.getRoom

Returns a room. Throws an error if the room isn’t found. This is a wrapper around the Get Room API and returns the same response.

const room = await liveblocks.getRoom("my-room-id");
// { type: "room", id: "my-room-id", metadata: {...}, ... }console.log(room);

Liveblocks.getOrCreateRoom

Get a room by its ID. If the room doesn’t exist, create it instead. The defaultAccesses option is required. Setting defaultAccesses to ["*:write"] creates a public room, whereas setting it to [] will create a private room that needs ID token permission to enter. Returns the same response as the Create Room API.

const room = await liveblocks.getOrCreateRoom("my-room-id", {  defaultAccesses: ["*:write"],});
// { type: "room", id: "my-room-id", metadata: {...}, ... }console.log(room);

A number of room creation options are available, allowing you to set permissions and attach custom metadata.

const room = await liveblocks.getOrCreateRoom("my-room-id", {  // The default room permissions. `[]` for private, `["*:write"]` for public.  defaultAccesses: [],
// Optional, the room's group ID permissions groupsAccesses: { design: ["*:write"], engineering: ["*:read"], },
// Optional, the room's user ID permissions usersAccesses: { "my-user-id": ["*:write"], },
// Optional, custom metadata to attach to the room metadata: { myRoomType: "whiteboard", },
// Optional, create it on a specific organization organizationId: "acme-corp",});

Group and user permissions are only used with ID token authorization, learn more about managing permission with ID tokens.

Liveblocks.updateRoom

Updates properties on a room. Throws an error if the room isn’t found. This is a wrapper around the Update Room API and returns the same response.

const room = await liveblocks.updateRoom("my-room-id", {  // The metadata or permissions you're updating  // ...});
// { type: "room", id: "my-room-id", metadata: {...}, ... }console.log(room);

Permissions and metadata properties can be updated on the room. Note that you need only pass the properties you’re updating. Setting a property to null will delete the property.

const room = await liveblocks.updateRoom("my-room-id", {  // Optional, update the default room permissions. `[]` for private, `["*:write"]` for public.  defaultAccesses: [],
// Optional, update the room's group ID permissions groupsAccesses: { design: ["*:write"], engineering: ["*:read"], },
// Optional, update the room's user ID permissions usersAccesses: { "my-user-id": ["*:write"], },
// Optional, custom metadata to update on the room metadata: { myRoomType: "whiteboard", },});

Group and user permissions are only used with ID token authorization, learn more about managing permission with ID tokens.

Liveblocks.upsertRoom

Update a room’s properties by its ID. If the room doesn’t exist, create it instead. The defaultAccesses option is required. Setting defaultAccesses to ["*:write"] creates a public room, whereas setting it to [] will create a private room that needs ID token permission to enter. Returns the same response as the Create Room API.

const room = await liveblocks.upsertRoom("my-room-id", {  // These fields will get updated when the room exists, or will be created  update: {    metadata: { color: "red" },  },  // These fields will only be set when the room will get created  create: {    defaultAccesses: ["*:write"],  },});
// { type: "room", id: "my-room-id", metadata: {...}, ... }console.log(room);

A number of room update or creation options are available, allowing you to set permissions and attach custom metadata.

const room = await liveblocks.upsertRoom("my-room-id", {  update: {    // The default room permissions. `[]` for private, `["*:write"]` for public.    defaultAccesses: [],
// Optional, the room's group ID permissions groupsAccesses: { design: ["*:write"], engineering: ["*:read"], },
// Optional, the room's user ID permissions usersAccesses: { "my-user-id": ["*:write"], },
// Optional, custom metadata to attach to the room metadata: { myRoomType: "whiteboard", }, },});

Group and user permissions are only used with ID token authorization, learn more about managing permission with ID tokens.

Liveblocks.deleteRoom

Deletes a room. If the room doesn’t exist, or has already been deleted, no error will throw. This is a wrapper around the Delete Room API and returns no response.

await liveblocks.deleteRoom("my-room-id");
Mass deleting rooms

If you need to delete multiple rooms at once, you can use liveblocks.iterRooms to efficiently iterate through rooms and delete them in batches. This example shows how to delete rooms in batches of 50 concurrent deletions at a time:

const MAX_CONCURRENT = 50;const queue: Promise<void>[] = [];
for await (const room of liveblocks.iterRooms({ // Optionally filter for certain rooms // ...})) { if (queue.length >= MAX_CONCURRENT) { await Promise.race(queue); }
const promise = liveblocks .deleteRoom(room.id) .finally(() => queue.splice(queue.indexOf(promise), 1));
queue.push(promise);}
await Promise.all(queue);

You can use any of the filtering options available in liveblocks.iterRooms to select which rooms to delete, such as filtering by metadata, room ID prefix, or user/group access.

Liveblocks.prewarmRoom

Speeds up connecting to a room for the next 10 seconds. Use this when you know a user will be connecting to a room with RoomProvider or enterRoom within 10 seconds, and the room will load quicker. This is a wrapper around the Prewarm Room API and returns no response.

await liveblocks.prewarmRoom("my-room-id");
Warm a room before navigating

Triggering a room directly before a user navigates to a room is an easy to way use this API. Here’s a Next.js server actions example, showing how to trigger prewarming with onPointerDown.

actions.ts
"use server";
import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: "",});
export async function prewarmRoom(roomId: string) { await liveblocks.prewarmRoom(roomId);}
RoomLink.tsx
"use client";
import { prewarmRoom } from "../actions";import Link from "next/link";
export function JoinButton({ roomId }: { roomId: string }) { return ( <Link href={`/rooms/${roomId}`} onPointerDown={() => prewarmRoom(roomId)}> {roomId} </Link> );}

onPointerDown is slightly quicker than onClick because it triggers before the user releases their pointer.

Liveblocks.updateRoomId

Permanently updates a room’s ID. newRoomId will replace currentRoomId. Note that this will disconnect connected users from the room, but this can be worked around. Throws an error if the room isn’t found. This is a wrapper around the Update Room API and returns the same response.

const room = await liveblocks.updateRoomId({  currentRoomId: "my-room-id",  newRoomId: "new-room-id",});
// { type: "room", id: "my-room-id", metadata: {...}, ... }console.log(room);
Redirect connected users to the new room

When a room’s ID is changed it disconnects all users that are currently connected. To redirect connected users to the new room you can use useErrorListener or room.subscribe("error") in your application to get the new room’s ID, and redirect users to the renamed room.

import { useErrorListener } from "@liveblocks/react/suspense";
function App() { useErrorListener((error) => { if (error.context.code === 4006) { // Room ID has been changed, get the new ID and redirect const newRoomId = error.message; (`https://example.com/document/${newRoomId}}`); } });}

Liveblocks.getActiveUsers

Returns a list of users that are currently present in the room. Throws an error if the room isn’t found. This is a wrapper around the Get Active Users API and returns the same response.

const activeUsers = await liveblocks.getActiveUsers("my-room-id");
// { data: [{ type: "user", id: "my-user-id", ... }, ...] }console.log(activeUsers);

Liveblocks.broadcastEvent

Broadcasts a custom event to the room. Throws an error if the room isn’t found. This is a wrapper around the Broadcast Event API and returns no response.

const customEvent = {  type: "EMOJI",  emoji: "🔥",};
await liveblocks.broadcastEvent("my-room-id", customEvent);

You can respond to custom events on the front end with useEventListener and room.subscribe("event"). When receiving an event sent with Liveblocks.broadcastEvent, user will be null and connectionId will be -1.

import { useEventListener } from "@liveblocks/react/suspense";
// When receiving an event sent from `@liveblocks/node`useEventListener(({ event, user, connectionId }) => { // `null` console.log(user);
// `-1` console.log(connectionId);});

Liveblocks.setPresence

Sets ephemeral presence for a user in a room without requiring a WebSocket connection. The presence data automatically expires after the specified TTL (time-to-live). This is useful for scenarios like showing an AI agent’s presence in a room. The presence is broadcast to all connected users in the room. This is a wrapper around the Set Presence API and returns no response on success.

await liveblocks.setPresence("my-room-id", {  userId: "agent-123",  data: {    status: "active",    cursor: { x: 100, y: 200 },  },  userInfo: {    name: "AI Assistant",    avatar: "https://example.com/avatar.png",  },  ttl: 60, // optional, 2–3599 seconds});
  • userId (required): The ID of the user to set presence for.
  • data (required): Presence data as a JSON object.
  • userInfo (optional): Metadata about the user or agent
  • ttl (optional): Time-to-live in seconds (minimum 2, maximum 3599). Defaults to 60. After this duration, the presence expires automatically.

Groups

Groups allow you to manage users for group mentions in comments and text editors.

Liveblocks.createGroup

Creates a new group with the specified members. This is a wrapper around the Create Group API and returns the same response.

const group = await liveblocks.createGroup({  groupId: "engineering-team",  memberIds: ["alice@example.com", "bob@example.com"],});
// { type: "group", id: "engineering-team", organizationId: "acme-corp", createdAt: "...", updatedAt: "...", scopes: { mention: true }, members: [...] }console.log(group);

You can also create a group without members and add them later:

const group = await liveblocks.createGroup({  groupId: "design-team",  // Optional, add members when creating the group  memberIds: ["charlie@example.com"],
// Optional, create it on a specific organization organizationId: "company-123",
// Optional, set group scopes (defaults to { mention: true }) scopes: { mention: true },});

Liveblocks.getGroup

Returns a group by its ID. Throws an error if the group isn’t found. This is a wrapper around the Get Group API and returns the same response.

const group = await liveblocks.getGroup({  groupId: "engineering-team",});
// { type: "group", id: "engineering-team", organizationId: "acme-corp", createdAt: "...", updatedAt: "...", scopes: { mention: true }, members: [...] }console.log(group);

Liveblocks.getGroups

Returns a list of all groups in your project. This is a wrapper around the Get Groups API and returns the same response.

const { data: groups, nextCursor } = await liveblocks.getGroups();
// A list of groups// [{ type: "group", id: "engineering-team", organizationId: "acme-corp", createdAt: "...", updatedAt: "...", scopes: { mention: true }, members: [...] }, ...]console.log(groups);
// A pagination cursor for the next pageconsole.log(nextCursor);

You can also paginate through groups:

const { data: groups, nextCursor } = await liveblocks.getGroups({  // Optional, the number of groups to return (defaults to 20)  limit: 50,
// Optional, cursor for pagination startingAfter: nextCursor,});

Liveblocks.getUserGroups

Returns all groups that a specific user is a member of. This is a wrapper around the Get User Groups API and returns the same response.

const { data: userGroups, nextCursor } = await liveblocks.getUserGroups({  userId: "alice@example.com",});
// A list of groups the user belongs to// [{ type: "group", id: "engineering-team", ... }, ...]console.log(userGroups);

You can also paginate through user groups:

const { data: userGroups, nextCursor } = await liveblocks.getUserGroups({  userId: "alice@example.com",  limit: 25,  startingAfter: "L3YyL2dyb3Vwcy...",});

Liveblocks.addGroupMembers

Adds new members to an existing group. This is a wrapper around the Add Group Members API and returns the same response.

const updatedGroup = await liveblocks.addGroupMembers({  groupId: "engineering-team",  memberIds: ["david@example.com", "eve@example.com"],});
// { type: "group", id: "engineering-team", organizationId: "acme-corp", createdAt: "...", updatedAt: "...", scopes: { mention: true }, members: [...] }console.log(updatedGroup);

Liveblocks.removeGroupMembers

Removes members from an existing group. This is a wrapper around the Remove Group Members API and returns the same response.

const updatedGroup = await liveblocks.removeGroupMembers({  groupId: "engineering-team",  memberIds: ["david@example.com"],});
// { type: "group", id: "engineering-team", organizationId: "acme-corp", createdAt: "...", updatedAt: "...", scopes: { mention: true }, members: [...] }console.log(updatedGroup);

Liveblocks.deleteGroup

Deletes a group. If the group doesn’t exist, no error will be thrown. This is a wrapper around the Delete Group API and returns no response.

await liveblocks.deleteGroup({  groupId: "old-team",});

Storage

Liveblocks.getStorageDocument

Returns the contents of a room’s Storage tree. By default, returns Storage in LSON format. Throws an error if the room isn’t found. This is a wrapper around the Get Storage Document API and returns the same response.

const storage = await liveblocks.getStorageDocument("my-room-id");

LSON is a custom Liveblocks format that preserves information about the conflict-free data types used. By default, getStorageDocument returns Storage in this format. This is the same as using "plain-json" in the second argument.

// Retrieve LSON Storage dataconst storage = await liveblocks.getStorageDocument("my-room-id", "plain-lson");
// If this were your Storage type...declare global { interface Liveblocks { Storage: { names: LiveList<string>; }; }}
// {// liveblocksType: "LiveObject",// data: {// names: {// liveblocksType: "LiveList",// data: ["Olivier", "Nimesh"],// }// }// }console.log(storage);

You can also retrieve Storage as JSON by passing "json" into the second argument.

// Retrieve JSON Storage dataconst storage = await liveblocks.getStorageDocument("my-room-id", "json");
// If this were your Storage type...declare global { interface Liveblocks { Storage: { names: LiveList<string>; }; }}
// {// names: ["Olivier", "Nimesh"]// }console.log(storage);

Liveblocks.initializeStorageDocument

Initializes a room’s Storage tree with given LSON data. To use this, the room must have already been created and have empty Storage. Throws an error if the room isn’t found. Calling this will disconnect all active users from the room. This is a wrapper around the Initialize Storage Document API and returns the same response.

// Create a new roomconst room = await liveblocks.createRoom("my-room-id", {  defaultAccesses: ["*:write"],});
// Initialize Storageconst storage = await liveblocks.initializeStorageDocument("my-room-id", { // Your LSON Storage value // ...});

LSON is a custom Liveblocks format that preserves information about conflict-free data types. The easiest way to create it is using the toPlainLson helper provided by @liveblocks/client. Note that your Storage root should always be a LiveObject.

import { toPlainLson, LiveList, LiveObject } from "@liveblocks/client";
// Create a new roomconst room = await liveblocks.createRoom("my-room-id", { defaultAccesses: ["*:write"],});
// If this were your Storage type...declare global { interface Liveblocks { Storage: { names: LiveList<string>; }; }}
// Create the initial conflict-free dataconst initialStorage: LiveObject<Liveblocks["Storage"]> = new LiveObject({ names: new LiveList(["Olivier", "Nimesh"]),});
// Convert to LSON and create Storageconst storage = await liveblocks.initializeStorageDocument( "my-room-id", toPlainLson(initialStorage));

It’s also possible to create plain LSON manually, without the helper function.

// Create a new roomconst room = await liveblocks.createRoom("my-room-id", {  defaultAccesses: ["*:write"],});
// If this were your Storage type...declare global { interface Liveblocks { Storage: { names: LiveList<string>; }; }}
// Create this Storage and add names to the LiveListconst storage = await liveblocks.initializeStorageDocument("my-room-id", { liveblocksType: "LiveObject", data: { names: { liveblocksType: "LiveList", data: ["Olivier", "Nimesh"], }, },});

Liveblocks.mutateStorage

Modify Storage contents from the server. No presence will be shown when you make changes.

// Mutate a single roomawait liveblocks.mutateStorage(  "my-room-id",
({ root }) => { root.get("list").push("item3"); });

The callback can be asynchronous, in which case a stream of mutations can happen over time.

// Mutate a single roomawait liveblocks.mutateStorage(  "my-room-id",
async ({ root }) => { // These changes happen immediately const animals = root.get("animals"); animals.clear(); animals.push("Thinking...");
await thinkForAWhile();
// These changes happen after `await` has run animals.clear(); animals.push("🐶"); animals.push("🦘"); });

Learn how to type your Storage.

Liveblocks.massMutateStorage

Modify Storage contents for multiple rooms simultaneously. With the default query value {} it will loop through every room in your project.

// Mutate a number of roomsawait liveblocks.massMutateStorage(  {},
// Callback runs on every selected room ({ room, root }) => { // { type: "room", id: "my-room-id", metadata: {...}, ... } console.log(room);
root.get("animals").push("🦍"); });

A number of options are also available, enabling you to filter for certain rooms. Additionally, you can set options for concurrency and provide an abort signal to cancel the mutations.

// Mutate a number of roomsawait liveblocks.massMutateStorage(  {    // Optional, filter for rooms that allow entry to group ID(s) in `groupsAccesses`    groupIds: ["engineering", "design"],
// Optional, filter for rooms that allow entry to a user's ID in `usersAccesses` userId: "my-user-id",
// Optional, use advanced filtering query: { // Optional, filter for rooms with an ID that starts with specific string roomId: { startsWith: "liveblocks:", }, // Optional, filter for rooms with custom metadata in `metadata` metadata: { roomType: "whiteboard", }, }, },
({ room, root }) => { // { type: "room", id: "my-room-id", metadata: {...}, ... } console.log(room);
root.get("animals").push("🦍"); },
// Optional { concurrency: 10, // Optional, process at most 10 rooms simultaneously signal, // Optional, provide an abort signal to cancel mutations mid-way });

Learn how to type your Storage.

Liveblocks.deleteStorageDocument

Deletes a room’s Storage data. Calling this will disconnect all active users from the room. Throws an error if the room isn’t found. This is a wrapper around the Delete Storage Document API and returns no response.

await liveblocks.deleteStorageDocument("my-room-id");

Liveblocks.uploadFile

Uploads a file to a room and returns a LiveFile which can then be added to the room's Storage tree.

const liveFile = await liveblocks.uploadFile({  roomId: "my-room-id",  file,});

Pass an AbortSignal in the optional second argument to cancel the upload.

const liveFile = await liveblocks.uploadFile(  {    roomId: "my-room-id",    file,  },  { signal });

Liveblocks.getFileUrl

Returns a presigned URL and its expiration time for a LiveFile. You can pass a LiveFile, its LiveFileData, or its ID. Throws an error if the room or file isn't found.

const { url, expiresAt } = await liveblocks.getFileUrl({  roomId: "my-room-id",  file: liveFile,});

Returns a StorageFileUrl object with the following properties:

  • urlstring

    A presigned URL for the file.

  • expiresAtstring

    The expiration time of the presigned URL.

Pass an AbortSignal in the optional second argument to cancel the request.

Yjs

Liveblocks.getYjsDocument

Returns a JSON representation of a room’s Yjs document. Throws an error if the room isn’t found. This is a wrapper around the Get Yjs Document API and returns the same response.

const yjsDocument = await liveblocks.getYjsDocument("my-room-id");
// { yourYText: "...", yourYArray: [...], ... }console.log(yjsDocument);

A number of options are available.

const yjsDocument = await liveblocks.getYjsDocument("my-room-id", {  // Optional, if true, `yText` values will return formatting  format: true,
// Optional, return a single key's value, e.g. `yDoc.get("my-key-id").toJson()` key: "my-key-id",
// Optional, override the inferred `key` type, e.g. "ymap" for `doc.get(key, Y.Map)` type: "ymap",});

Liveblocks.sendYjsBinaryUpdate

Send a Yjs binary update to a room’s Yjs document. You can use this to update or initialize the room’s Yjs document. Throws an error if the room isn’t found. This is a wrapper around the Send a Binary Yjs Update API and returns no response.

await liveblocks.sendYjsBinaryUpdate("my-room-id", update);

Here’s an example of how to update a room’s Yjs document with your changes.

import * as Y from "yjs";
// Create a Yjs documentconst yDoc = new Y.Doc();
// Create your data structures and make your update// If you're using a text editor, you need to match its formatconst yText = yDoc.getText("text");yText.insert(0, "Hello world");
// Encode the document state as an updateconst update = Y.encodeStateAsUpdate(yDoc);
// Send update to Liveblocksawait liveblocks.sendYjsBinaryUpdate("my-room-id", update);

To update a subdocument instead of the main document, pass its guid.

await liveblocks.sendYjsBinaryUpdate("my-room-id", update, {  // Optional, update a subdocument instead. guid is its unique identifier  guid: "c4a755...",});

To create a new room and initialize its Yjs document, call liveblocks.createRoom before sending the binary update.

// Create new roomconst room = await liveblocks.createRoom("my-room-id");
// Set initial Yjs document valueawait liveblocks.sendYjsBinaryUpdate("my-room-id", state);
Different editors

Note that each text and code editor handles binary updates in a different way, and may use a different Yjs shared type, for example Y.XmlFragment instead of Y.Text.

Create a binary update with Slate:

Create a binary update with Tiptap:

Read the Yjs documentation to learn more about creating binary updates.

Liveblocks.getYjsDocumentAsBinaryUpdate

Return a room’s Yjs document as a single binary update. You can use this to get a copy of your Yjs document in your back end. Throws an error if the room isn’t found. This is a wrapper around the Get Yjs Document Encoded as a Binary Yjs Update API and returns the same response.

const binaryYjsUpdate =  await liveblocks.getYjsDocumentAsBinaryUpdate("my-room-id");

To return a subdocument instead of the main document, pass its guid.

const binaryYjsUpdate = await liveblocks.getYjsDocumentAsBinaryUpdate(  "my-room-id",  {    // Optional, return a subdocument instead. guid is its unique identifier    guid: "c4a755...",  });

Read the Yjs documentation to learn more about using binary updates.

Version History

Liveblocks.getVersionHistory

Returns a room’s version history snapshots, sorted by creation date from newest to oldest. Throws an error if the room isn’t found. This is a wrapper around the Get Version History API and returns the same response.

const { data: versions, nextCursor } = await liveblocks.getVersionHistory(  "my-room-id",  {    // Optional, defaults to 20    limit: 20,
// Optional, used for pagination cursor: "eyJjcmVhdGVkQXQi...", });

Liveblocks.createVersionHistorySnapshot

Creates a new version history snapshot of a room, capturing both its Storage and Yjs documents. Throws an error if the room isn’t found. This is a wrapper around the Create Version History Snapshot API and returns the same response.

const { data } = await liveblocks.createVersionHistorySnapshot("my-room-id");
// { id: "vh_d75sF3..." }console.log(data);

Liveblocks.getYjsVersion

Returns a specific version of a room’s Yjs document encoded as a binary Yjs update. Throws an error if the room or version isn’t found. This is a wrapper around the Get Yjs Document Version API and returns the same response.

const binaryYjsUpdate = await liveblocks.getYjsVersion({  roomId: "my-room-id",  versionId: "vh_d75sF3...",});

Liveblocks.deleteVersion

Permanently deletes a version from a room’s history. Throws an error if the room or version isn’t found. This is a wrapper around the Delete Version API and returns no response.

await liveblocks.deleteVersion({  roomId: "my-room-id",  versionId: "vh_d75sF3...",});

Attachments

Liveblocks.uploadAttachment

Uploads a file to a room and returns a CommentAttachment which can be added to a comment. The userId must match the comment's user ID.

const attachment = await liveblocks.uploadAttachment({  roomId: "my-room-id",  userId: "pierre@example.com",  file,});
const comment = await liveblocks.createComment({ roomId: "my-room-id", threadId: "th_d75sF3...", data: { userId: "pierre@example.com", body, attachmentIds: [attachment.id], },});

You can also pass uploaded attachment IDs to data.comment.attachmentIds in createThread, or to data.attachmentIds in editComment. When editing, pass the ID of every attachment that should remain on the comment; pass an empty array to remove them all. A comment can have up to 10 attachments.

Pass an AbortSignal in the optional second argument to cancel the upload.

const attachment = await liveblocks.uploadAttachment(  {    roomId: "my-room-id",    userId: "pierre@example.com",    file,  },  { signal });

Liveblocks.getAttachment

Returns an attachment's metadata and a presigned download URL. Throws an error if the room or attachment isn't found.

const attachment = await liveblocks.getAttachment({  roomId: "my-room-id",  attachmentId: "at_d75sF3...",});
// { type: "attachment", id: "at_d75sF3...", name: "document.pdf", ... }console.log(attachment);
// The presigned URL to download the attachmentconsole.log(attachment.url);

Returns an AttachmentWithUrl object with the following properties:

  • type"attachment"

    The type of the object.

  • idstring

    The attachment ID (starts with "at_").

  • namestring

    The name of the attachment file.

  • mimeTypestring

    The MIME type of the attachment.

  • sizenumber

    The size of the attachment in bytes.

  • urlstring

    A presigned URL to download the attachment.

  • expiresAtstring

    The expiration time of the presigned URL.

Comments

Liveblocks.getThreads

Returns a list of threads found inside a room. Throws an error if the room isn’t found. This is a wrapper around the Get Room Threads API and returns the same response.

const { data: threads } = await liveblocks.getThreads({  roomId: "my-room-id",});
// [{ type: "thread", id: "th_d75sF3...", ... }, ...]console.log(threads);

It’s also possible to filter threads by visibility, resolved status, and their string, boolean, and number metadata using a query parameter. You can also pass startsWith to match the start of a string.

const { data: threads } = await liveblocks.getThreads({  roomId: "my-room-id",
// Optional, use advanced filtering query: { // Optional, filter based on resolved status resolved: false,
// Optional, filter based on visibility visibility: "private",
// Optional, filter for metadata values metadata: { status: "open", pinned: true, priority: 3,
// You can match the start of a metadata string organization: { startsWith: "liveblocks:", }, }, },});

You can also pass a query language string instead of a query object.

Liveblocks.createThread

Creates a new thread within a specific room, using room ID and thread data. Threads are public by default, but can be created as private by passing visibility: "private". This is a wrapper around the Create Thread API and returns the new thread.

const thread = await liveblocks.createThread({  roomId: "my-room-id",
data: { comment: { userId: "florent@example.com", body: { version: 1, content: [ /* The comment's body text goes here, see below */ ], }, }, },});
// { type: "thread", id: "th_d75sF3...", ... }console.log(thread);

A comment’s body is an array of paragraphs, each containing child nodes. Here’s an example of how to construct a comment’s body, which can be submitted under data.comment.body.

import { CommentBody } from "@liveblocks/node";
const body: CommentBody = { version: 1, content: [ { type: "paragraph", children: [{ text: "Hello " }, { text: "world", bold: true }], }, ],};
const thread = await liveblocks.createThread({ roomId: "my-room-id",
data: { // ... comment: { // The comment's body, uses the `CommentBody` type body,
// ... }, },});
Creating a comment from Markdown

You can also convert a Markdown string to a CommentBody with markdownToCommentBody.

This method has a number of options, allowing for custom metadata, thread visibility, and a creation date for the comment.

const thread = await liveblocks.createThread({  roomId: "my-room-id",
data: { // Optional, custom metadata properties metadata: { color: "blue", page: 3, pinned: true, },
// Optional, defaults to "public" visibility: "private",
// Data for the first comment in the thread comment: { // The ID of the user that created the comment userId: "florent@example.com",
// Optional, when the comment was created. createdAt: new Date(),
// Optional, custom comment metadata metadata: { tag: "important", spam: false, },
// Optional, IDs of uploaded comment attachments attachmentIds: [attachment.id],
// The comment's body, uses the `CommentBody` type body: { version: 1, content: [ /* The comment's body text goes here, see above */ ], }, }, },});
// { type: "thread", id: "th_d75sF3...", ... }console.log(thread);

Liveblocks.getThread

Returns a thread. Throws an error if the room or thread isn’t found. This is a wrapper around the Get Thread API and returns the same response.

const thread = await liveblocks.getThread({  roomId: "my-room-id",  threadId: "th_d75sF3...",});
// { type: "thread", id: "th_d75sF3...", ... }console.log(thread);

Liveblocks.editThreadMetadata

Updates the metadata of a specific thread within a room. This method allows you to modify the metadata of a thread, including user information and the date of the last update. Throws an error if the room or thread isn’t found. This is a wrapper around the Update Thread Metadata API and returns the updated metadata.

const editedMetadata = await liveblocks.editThreadMetadata({  roomId: "my-room-id",  threadId: "th_d75sF3...",
data: { metadata: { color: "yellow", }, userId: "marc@example.com", updatedAt: new Date(), // Optional },});
// { color: "yellow", page: 3, pinned: true }console.log(editedMetadata);

Metadata can be a string, number, or boolean. You can also use null to remove metadata from a thread. Here’s an example using every option.

const editedMetadata = await liveblocks.editThreadMetadata({  roomId: "my-room-id",  threadId: "th_d75sF3...",
data: { // Custom metadata metadata: { // Metadata can be a string, number, or boolean title: "My thread title", page: 3, pinned: true,
// Remove metadata with null color: null, },
// The ID of the user that updated the metadata userId: "marc@example.com",
// Optional, the time the user updated the metadata updatedAt: new Date(), },});
// { title: "My thread title", page: 3, pinned: true }console.log(editedMetadata);

Liveblocks.editCommentMetadata

Updates the metadata of a specific comment within a thread. This method allows you to modify the metadata of a comment, including user information and the date of the last update. Throws an error if the room, thread, or comment isn’t found. This is a wrapper around the Update Comment Metadata API and returns the updated metadata.

const editedMetadata = await liveblocks.editCommentMetadata({  roomId: "my-room-id",  threadId: "th_d75sF3...",  commentId: "cm_agH76a...",
data: { metadata: { spam: false, }, userId: "stacy@example.com", updatedAt: new Date(), // Optional },});
// { spam: false }console.log(editedMetadata);

Metadata can be a string, number, or boolean. You can also use null to remove metadata from a comment. Here’s an example using every option.

const editedMetadata = await liveblocks.editCommentMetadata({  roomId: "my-room-id",  threadId: "th_d75sF3...",  commentId: "cm_agH76a...",
data: { // Custom metadata metadata: { // Metadata can be a string, number, or boolean tag: "important", priority: 2, spam: true,
// Remove metadata with null assignedTo: null, },
// The ID of the user that updated the metadata userId: "stacy@example.com",
// Optional, the time the user updated the metadata updatedAt: new Date(), },});
// { tag: "important", priority: 2, flagged: true }console.log(editedMetadata);

Liveblocks.markThreadAsResolved

Marks a thread as resolved, which means it sets the resolved property on the specified thread to true. Takes a userId, which is the ID of the user that resolved the thread. Throws an error if the room or thread isn’t found. This is a wrapper around the Mark Thread As Resolved API and returns the same response.

const thread = await liveblocks.markThreadAsResolved({  roomId: "my-room-id",  threadId: "th_d75sF3...",  data: {    userId: "steven@example.com",  },});
// { type: "thread", id: "th_d75sF3...", ... }console.log(thread);

Liveblocks.markThreadAsUnresolved

Marks a thread as unresolved, which means it sets the resolved property on the specified thread to false. Takes a userId, which is the ID of the user that unresolved the thread. Throws an error if the room or thread isn’t found. This is a wrapper around the Mark Thread As Unresolved API and returns the same response.

const thread = await liveblocks.markThreadAsUnresolved({  roomId: "my-room-id",  threadId: "th_d75sF3...",  data: {    userId: "steven@example.com",  },});
// { type: "thread", id: "th_d75sF3...", ... }console.log(thread);

Liveblocks.deleteThread

Deletes a thread. Throws an error if the room or thread isn’t found. This is a wrapper around the Delete Thread API and returns no response.

await liveblocks.deleteThread({  roomId: "my-room-id",  threadId: "th_d75sF3...",});

Liveblocks.subscribeToThread

Subscribes a user to a thread, meaning they will receive inbox notifications when new comments are posted. Throws an error if the room or thread isn’t found. This is a wrapper around the Subscribe To Thread API and returns the same response.

const subscription = await liveblocks.subscribeToThread({  roomId: "my-room-id",  threadId: "th_d75sF3...",  data: {    userId: "steven@example.com",  },});
// { kind: "thread", subjectId: "th_d75sF3...", ... }console.log(subscription);

Subscribing will replace any existing subscription for the current thread set at room-level. This value can also be overridden by a room-level call that is run afterwards.

const roomId = "my-room-id";const userId = "steven@example.com";
// 1. Disables notifications for all threadsawait liveblocks.updateRoomSubscriptionSettings({ roomId, userId, data: { threads: "none", },});
// 2. Enables notifications just for this thread, "th_d75sF3..."await liveblocks.subscribeToThread({ roomId, threadId: "th_d75sF3...", data: { userId },});
// 3. Disables notifications for all threads, including "th_d75sF3..."await liveblocks.updateRoomSubscriptionSettings({ roomId, userId, data: { threads: "none", },});

Liveblocks.unsubscribeFromThread

Unsubscribes a user from a thread, meaning they will no longer receive inbox notifications when new comments are posted. Throws an error if the room or thread isn’t found. This is a wrapper around the Unsubscribe From Thread API and returns the same response.

await liveblocks.unsubscribeFromThread({  roomId: "my-room-id",  threadId: "th_d75sF3...",  data: {    userId: "steven@example.com",  },});

Unsubscribing will replace any existing subscription for the current thread set at room-level. This value can also be overridden by a room-level call that is run afterwards.

const roomId = "my-room-id";const userId = "steven@example.com";
// 1. Enables notifications for all thread activityawait liveblocks.updateRoomSubscriptionSettings({ roomId, userId, data: { threads: "all", },});
// 2. Disables notifications just for this thread, "th_d75sF3..."await liveblocks.unsubscribeFromThread({ roomId, threadId: "th_d75sF3...", data: { userId },});
// 3. Enables notifications for all thread activity, including "th_d75sF3..."await liveblocks.updateRoomSubscriptionSettings({ roomId, userId, data: { threads: "none", },});

Liveblocks.getThreadSubscriptions

Gets a thread’s subscriptions, returning a list of users that will receive notifications when new comments are posted. Throws an error if the room or thread isn’t found. This is a wrapper around the Get Thread Subscriptions API and returns the same response.

const { data: subscriptions } = await liveblocks.getThreadSubscriptions({  roomId: "my-room-id",  threadId: "th_d75sF3...",});
// [{ kind: "thread", subjectId: "th_d75sF3...", userId: "steven@example.com", ... }, ...]console.log(subscriptions);

Liveblocks.createComment

Creates a new comment in a specific thread within a room. This method allows users to add comments to a conversation thread, specifying the user who made the comment and the content of the comment. This method is a wrapper around the Create Comment API and returns the new comment.

const comment = await liveblocks.createComment({  roomId: "my-room-id",  threadId: "th_d75sF3...",
data: { body: { version: 1, content: [ /* The comment's body text goes here, see below */ ], }, userId: "pierre@example.com", createdAt: new Date(), // Optional },});

A comment’s body is an array of paragraphs, each containing child nodes. Here’s an example of how to construct a comment’s body, which can be submitted under data.body.

import { CommentBody } from "@liveblocks/node";
const body: CommentBody = { version: 1, content: [ { type: "paragraph", children: [{ text: "Hello " }, { text: "world", bold: true }], }, ],};
const comment = await liveblocks.createComment({ roomId: "my-room-id", threadId: "th_d75sF3...",
data: { // The comment's body, uses the `CommentBody` type body,
// ... },});
Creating a comment from Markdown

You can also convert a Markdown string to a CommentBody with markdownToCommentBody.

This method has a number of options, including the option to add a custom creation date and metadata to the comment.

const comment = await liveblocks.createComment({  roomId: "my-room-id",  threadId: "th_d75sF3...",
data: { // The comment's body, uses the `CommentBody` type body: { version: 1, content: [ /* The comment's body text goes here, see above */ ], },
// The ID of the user that created the comment userId: "adrien@example.com",
// Optional, the time the comment was created createdAt: new Date(),
// Optional, custom comment metadata metadata: { tag: "important", reviewed: false, },
// Optional, IDs of uploaded comment attachments attachmentIds: [attachment.id], },});

Liveblocks.getComment

Returns a comment. Throws an error if the room, thread, or comment isn’t found. This is a wrapper around the Get Comment API and returns the same response.

const comment = await liveblocks.getComment({  roomId: "my-room-id",  threadId: "th_d75sF3...",  commentId: "cm_agH76a...",});
// { type: "comment", threadId: "th_d75sF3...", ... }console.log(comment);

Liveblocks.editComment

Edits an existing comment in a specific thread within a room. This method allows users to update the content of their previously posted comments, with the option to specify the time of the edit. Throws an error if the comment isn’t found. This is a wrapper around the Edit Comment API and returns the updated comment.

const editedComment = await liveblocks.editComment({  roomId: "my-room-id",  threadId: "th_d75sF3...",  commentId: "cm_agH76a...",
data: { body: { version: 1, content: [ /* The comment's body text goes here, see below */ ], },
// Optional, the time the comment was edited editedAt: new Date(),
// Optional, custom comment metadata metadata: { tag: "important", spam: false, },
// Optional, IDs of every attachment that should remain on the comment attachmentIds: [attachment.id], },});
// { type: "comment", threadId: "th_d75sF3...", ... }console.log(editedComment);

A comment’s body is an array of paragraphs, each containing child nodes. Here’s an example of how to construct a comment’s body, which can be submitted under data.body.

import { CommentBody } from "@liveblocks/node";
const body: CommentBody = { version: 1, content: [ { type: "paragraph", children: [{ text: "Hello " }, { text: "world", bold: true }], }, ],};
const editedComment = await liveblocks.editComment({ roomId: "my-room-id", threadId: "th_d75sF3...", commentId: "cm_agH76a...",
data: { // The comment's body, uses the `CommentBody` type body,
// ... },});
Creating a comment from Markdown

You can also convert a Markdown string to a CommentBody with markdownToCommentBody.

Liveblocks.deleteComment

Deletes a specific comment from a thread within a room. If there are no remaining comments in the thread, the thread is also deleted. This method throws an error if the comment isn’t found. This is a wrapper around the Delete Comment API and returns no response.

await liveblocks.deleteComment({  roomId: "my-room-id",  threadId: "th_d75sF3...",  commentId: "cm_agH76a...",});

Liveblocks.addCommentReaction

Adds a reaction to a specific comment in a thread within a room. Throws an error if the comment isn’t found or if the user has already added the same reaction on the comment. This is a wrapper around the Add Comment Reaction API and returns the new reaction.

const reaction = await liveblocks.addCommentReaction({  roomId: "my-room-id",  threadId: "th_d75sF3...",  commentId: "cm_agH76a...",
data: { emoji: "👨‍👩‍👧", userId: "guillaume@example.com", createdAt: new Date(), // Optional, the time the reaction was added },});
// { emoji: "👨‍👩‍👧", userId "guillaume@example.com", ... }console.log(reaction);

Liveblocks.removeCommentReaction

Removes a reaction from a specific comment in a thread within a room. Throws an error if the comment reaction isn’t found. This is a wrapper around the Remove Comment Reaction API and returns no response.

await liveblocks.removeCommentReaction({  roomId: "my-room-id",  threadId: "th_d75sF3...",  commentId: "cm_agH76a...",
data: { emoji: "👨‍👩‍👧", userId: "steven@example.com", removedAt: new Date(), // Optional, the time the reaction is to be removed },});

Liveblocks.getRoomSubscriptionSettings

Returns a user’s subscription settings for a specific room, specifying which thread and textMention inbox notifications they are set to receive. This is a wrapper around the Get Room Subscription Settings API.

const subscriptionSettings = await liveblocks.getRoomSubscriptionSettings({  roomId: "my-room-id",  userId: "steven@example.com",});
// { threads: "all", textMentions: "mine" }console.log(subscriptionSettings);

For "threads", these are the three possible values:

  • "all" Receive notifications for every activity in every thread.
  • "replies_and_mentions" Receive notifications for mentions and threads you’re participating in.
  • "none" No notifications are received.

For "textMentions", these are the two possible values:

  • "mine" Receive notifications for mentions of you.
  • "none" No notifications are received.

Liveblocks.updateRoomSubscriptionSettings

Updates a user’s subscription settings for a specific room, defining which thread and textMention inbox notifications they will receive. This is a wrapper around the Update Room Subscription Settings API.

const updatedSubscriptionSettings =  await liveblocks.updateRoomSubscriptionSettings({    roomId: "my-room-id",    userId: "steven@example.com",    data: {      threads: "replies_and_mentions",      textMentions: "mine",    },  });
// { threads: "replies_and_mentions", ... }console.log(updatedSubscriptionSettings);

For "threads", these are the three possible values that can be set:

  • "all" Receive notifications for every activity in every thread.
  • "replies_and_mentions" Receive notifications for mentions and threads you’re participating in.
  • "none" No notifications are received.

For "textMentions", these are the two possible values that can be set:

  • "mine" Receive notifications for mentions of you.
  • "none" No notifications are received.
Replacing individual thread subscriptions

Subscribing will replace any existing thread subscriptions in the current room. This value can also be overridden by a room-level call that is run afterwards.

const roomId = "my-room-id";const userId = "steven@example.com";
// 1. Enables notifications just for this thread, "th_d75sF3..."await liveblocks.subscribeToThread({ roomId, threadId: "th_d75sF3...", data: { userId },});
// 2. Disables notifications for all threads, including "th_d75sF3..."await liveblocks.updateRoomSubscriptionSettings({ roomId, userId, data: { threads: "none", },});

Liveblocks.deleteRoomSubscriptionSettings

Deletes a user’s subscription settings for a specific room. This is a wrapper around the Delete Room Subscription Settings API.

await liveblocks.deleteRoomSubscriptionSettings({  roomId: "my-room-id",  userId: "steven@example.com",});

Liveblocks.getUserRoomSubscriptionSettings

Returns a list of a user’s subscription settings for all rooms. This is a wrapper around the Get User Room Subscription Settings API.

const { data: subscriptionSettings, nextCursor } =  await liveblocks.getUserRoomSubscriptionSettings({    userId: "steven@example.com",
// Optional, filter for a specific organization organizationId: "acme-corp", });
console.log(subscriptionSettings);
// { roomId: "my-room-id", threads: "all", ... }
// Paginationif (nextCursor) { const { data: nextPage } = await liveblocks.getUserRoomSubscriptionSettings({ userId: "steven@example.com", startingAfter: nextCursor, });}

Feeds

Liveblocks.getFeeds

Returns a list of feeds in a room. This is a wrapper around the Get Room Feeds API and returns the same response.

const { data: feeds } = await liveblocks.getFeeds({  roomId: "my-room-id",});
// [{ feedId: "feed-1", metadata: {...}, timestamp: 1234567890 }, ...]console.log(feeds);

Liveblocks.createFeed

Creates a new feed in a room. This is a wrapper around the Create Feed API and returns the created feed.

const feed = await liveblocks.createFeed({  roomId: "my-room-id",  feedId: "my-feed-id",
// Optional, custom metadata for the feed metadata: { name: "My Feed", channel: true, },
// Optional, timestamp in milliseconds. Defaults to current time if not provided timestamp: Date.now(),});
// { feedId: "my-feed-id", metadata: {...}, timestamp: 1234567890 }console.log(feed);

Liveblocks.getFeed

Returns a feed by its ID. This is a wrapper around the Get Feed API and returns the same response.

const feed = await liveblocks.getFeed({  roomId: "my-room-id",  feedId: "my-feed-id",});
// { feedId: "my-feed-id", metadata: {...}, timestamp: 1234567890 }console.log(feed);

Liveblocks.updateFeed

Updates the metadata of a feed. This is a wrapper around the Update Feed API.

await liveblocks.updateFeed({  roomId: "my-room-id",  feedId: "my-feed-id",  metadata: {    name: "Updated Feed Name",    updated: new Date().toISOString(),  },});

Liveblocks.deleteFeed

Deletes a feed. This is a wrapper around the Delete Feed API.

await liveblocks.deleteFeed({  roomId: "my-room-id",  feedId: "my-feed-id",});

Liveblocks.getFeedMessages

Returns a list of messages in a feed. This is a wrapper around the Get Feed Messages API and returns the same response.

const { data: messages } = await liveblocks.getFeedMessages({  roomId: "my-room-id",  feedId: "my-feed-id",});
// [{ id: "msg-1", timestamp: 1234567890, data: {...} }, ...]console.log(messages);

Liveblocks.createFeedMessage

Creates a new message in a feed. This is a wrapper around the Create Feed Message API and returns the created message.

const message = await liveblocks.createFeedMessage({  roomId: "my-room-id",  feedId: "my-feed-id",
// The message data data: { role: "user", content: "Hello, world!", },
// Optional, custom message ID. One will be generated if not provided id: "my-message-id",
// Optional, timestamp in milliseconds. Defaults to current time if not provided timestamp: Date.now(),});
// { id: "my-message-id", timestamp: 1234567890, data: {...} }console.log(message);

Liveblocks.updateFeedMessage

Updates a feed message. This is a wrapper around the Update Feed Message API.