API Reference@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 is new in 1.2, and offers access to our REST API.

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

Authorization

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

Liveblocks.prepareSession

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.

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",    },  });

    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.

  2. Decide which permissions to allow this session

    session.allow("my-room-1", session.FULL_ACCESS);session.allow("my-room-2", session.FULL_ACCESS);session.allow("my-room-3", session.FULL_ACCESS);session.allow("my-team:*", session.READ_ACCESS);
  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, session.FULL_ACCESS); }
// 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 });}

Liveblocks.identifyUser

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.

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 { 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.

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 });}

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, nextPage } = 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 pagination URL used for retrieving the next page of results with the REST API// "/v2/rooms?startingAfter=L3YyL3Jvb21z..."console.log(nextPage);

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

const {  data: rooms,  nextCursor,  nextPage,} = 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, 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;}

Liveblocks.createRoom

Programmatically creates a new room from a room ID. The defaultAccesses option is required. Setting defaultAccesses to ["room: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: ["room:write"],});
// { type: "room", id: "my-room-id", ... }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, `["room:write"]` for public.  defaultAccesses: [],
// Optional, the room's group ID permissions groupsAccesses: { design: ["room:write"], engineering: ["room:presence:write", "room:read"], },
// Optional, the room's user ID permissions usersAccesses: { "my-user-id": ["room: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.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", ... }console.log(room);

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", ... }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, `["room:write"]` for public.  defaultAccesses: [],
// Optional, update the room's group ID permissions groupsAccesses: { design: ["room:write"], engineering: ["room:presence:write", "room:read"], },
// Optional, update the room's user ID permissions usersAccesses: { "my-user-id": ["room: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.deleteRoom

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

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

Liveblocks.updateRoomId

Permanently updates a room’s ID. newRoomId will replace roomId. 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({  roomId: "my-room-id",  newRoomId: "new-room-id",});
// { type: "room", id: "new-room-id", ... }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.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 useBroadcastEvent 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);});

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: ["room: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: ["room: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: ["room: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.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");

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.

Schema validation

Liveblocks.createSchema

Creates a schema that can be used to enforce a room’s Storage data structure. The schema consists of a unique name, and a body which specifies the data shape of the room in Liveblocks schema syntax. This is a wrapper around the Create Schema API and returns the same response.

const schemaBody = `  type Storage {    names: LiveList<string>  }`;
const schema = await liveblocks.createSchema("my-schema-name", schemaBody);
// { id: "my-schema-name@1", name: "my-schema-name", version: 1, ... }console.log(schema);

Read the schema validation page to learn more.

Liveblocks.getSchema

Returns a schema from its ID. A schema’s ID is a combination of its name and version, for example the ID for version 1 of my-schema-name is my-schema-name@1. This is a wrapper around the Get Schema API and returns the same response.

const updatedBody = `  type Storage {    names: LiveMap<string, string>  }`;
const schema = await liveblocks.getSchema("my-schema-name@1", updatedBody);
// { id: "my-schema-name@1", name: "my-schema-name", version: 1, ... }console.log(schema);

Read the schema validation page to learn more.

Liveblocks.updateSchema

Updates a schema’s body and increments its version. A schema’s body specifies the data shape of the room in Liveblocks schema syntax. Find the schema by its ID, a combination of its name and version, for example the ID for version 1 of my-schema-name is my-schema-name@1. This is a wrapper around the Update Schema API and returns the same response.

const schema = await liveblocks.updateSchema("my-schema-name@1");
// { id: "my-schema-name@1", name: "my-schema-name", version: 1, ... }console.log(schema);

Read the schema validation page to learn more.

Liveblocks.deleteSchema

Deletes a schema. This is only allowed if the schema is not attached to a room. Find the schema by its ID, a combination of its name and version, for example the ID for version 1 of my-schema-name is my-schema-name@1. This is a wrapper around the Delete Schema API and returns no response.

await liveblocks.deleteSchema("my-schema-name@1");

Read the schema validation page to learn more.

Liveblocks.getSchemaByRoomId

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

const schema = await liveblocks.getSchemaByRoomId("my-room-id");
// { id: "my-schema-name@1", name: "my-schema-name", version: 1, ... }console.log(schema);

Read the schema validation page to learn more.

Liveblocks.attachSchemaToRoom

Attaches a schema to a room, and instantly enables runtime schema validation in it. Attach the schema by its ID, a combination of its name and version, for example the ID for version 1 of my-schema-name is my-schema-name@1. This is a wrapper around the Attach Schema to a Room API and returns the same response.

If the current contents of the room’s Storage do not match the schema, attaching will fail and the error message will give details on why the schema failed to attach. It’ll also throw an error if the room isn’t found.

const schema = await liveblocks.attachSchemaToRoom(  "my-room-id",  "my-schema-name@1");
// { id: "my-schema-name@1", name: "my-schema-name", version: 1, ... }console.log(schema);

Read the schema validation page to learn more.

Liveblocks.detachSchemaFromRoom

Detaches a schema from a room. This is a wrapper around the Detach Schema from a Room API and returns no response.

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

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 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 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. 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,
// ... }, },});

This method has a number of options, allowing for custom metadata 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, },
// 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(),
// 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.getThreadParticipants

Returns a list of participants found inside a thread. A participant is a user who has commented or been mentioned in the thread. Throws an error if the room or thread isn’t found. This is a wrapper around the Get Thread Participants API and returns the same response.

const { participantIds } = await liveblocks.getThreadParticipants({  roomId: "my-room-id",  threadId: "th_d75sF3...",});
// ["chris@example.com", "nimesh@example.com", ...]console.log(participantIds);

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.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.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 Get 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,
// ... },});

This method has a number of options, including the option to add a custom creation date 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",
// The time the comment was created createdAt: new Date(), },});

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 */ ], }, userId: "alicia@example.com", createdAt: new Date(), // Optional },});
// { 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 comment = await liveblocks.createComment({ roomId: "my-room-id", threadId: "th_d75sF3...",
data: { // The comment's body, uses the `CommentBody` type body,
// ... },});
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: { version: 1, content: [ /* The comment's body text goes here, see above */ ], },
// The ID of the user that edited the comment userId: "alicia@example.com",
// Optional, the time the comment was edited editedAt: new Date(), },});
// { type: "comment", threadId: "th_d75sF3...", ... }console.log(editedComment);

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.getRoomNotificationSettings

Returns a user’s notification settings for a specific room. This is a wrapper around the Get Room Notification Settings API.

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

Liveblocks.updateRoomNotificationSettings

Updates a user’s notification settings for a specific room. This is a wrapper around the Update Room Notification Settings API.

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

Liveblocks.deleteRoomNotificationSettings

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

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

Notifications

Liveblocks.getInboxNotifications

Returns a list of a user’s inbox notifications. This is a wrapper around the Get Inbox Notifications API. It also provides an unread query parameter to filter unread notifications.

const { data: inboxNotifications } = await liveblocks.getInboxNotifications({  userId: "steven@example.com",});
// [{ id: "in_3dH7sF3...", kind: "thread", ... }, { id: "in_3dH7sF3...", kind: "textMention", ... }, ...]console.log(inboxNotifications);
// Filter unread notificationsconst { data: unreadInboxNotifications } = await liveblocks.getInboxNotifications({ userId: "steven@example.com", query: { unread: true, }, });

Liveblocks.getInboxNotification

Returns a user’s inbox notification. This is a wrapper around the Get Inbox Notification API.

const inboxNotification = await liveblocks.getInboxNotification({  userId: "steven@example.com",  inboxNotificationId: "in_3dH7sF3...",});
// { id: "in_3dH7sF3...", kind: "thread", ... }// or { id: "in_3dH7sF3...", kind: "textMention", ... }// or { id: "in_3dH7sF3...", kind: "$yourKind", ... }console.log(inboxNotification);

Liveblocks.triggerInboxNotification

Triggers a custom inbox notification. kind must start with a $, and represents the type of notification. activityData is used to send custom data with the notification, and properties can have string, number, or boolean values. This is a wrapper around the Trigger Inbox Notification API.

await liveblocks.triggerInboxNotification({  // The ID of the user that will receive the inbox notification  userId: "steven@example.com",
// The custom notification kind, must start with a $ kind: "$fileUploaded",
// Custom ID for this specific notification subjectId: "my-file",
// Custom data related to the activity that you need to render the inbox notification activityData: { // Data can be a string, number, or boolean file: "https://example.com/my-file.zip", size: 256, success: true, },
// Optional, define the room ID the notification was sent from roomId: "my-room-id",});
Typing custom notifications

To type custom notifications, edit the ActivitiesData type in your config file.

liveblocks.config.ts
declare global {  interface Liveblocks {    // Custom activities data for custom notification kinds    ActivitiesData: {      // Example, a custom $alert kind      $alert: {        title: string;        message: string;      };    };
// Other kinds // ... }}

Liveblocks.deleteInboxNotification

Deletes a user’s inbox notification. This is a wrapper around the Delete Inbox Notification API.

await liveblocks.deleteInboxNotification({  userId: "steven@example.com",  inboxNotificationId: "in_3dH7sF3...",});

Liveblocks.deleteAllInboxNotifications

Deletes all the user’s inbox notifications. This is a wrapper around the Delete Inbox Notifications API.

await liveblocks.deleteAllInboxNotifications({  userId: "steven@example.com",});

Error handling

Errors in our API methods, such as network failures, invalid arguments, or server-side issues, are reported through the LiveblocksError class. This custom error class extends the standard JavaScript Error and includes a status property, which provides the HTTP status code for the error, such as 404 for not found or 500 for server errors.

Example of handling errors in a typical API call:

try {  const room = await liveblocks.getRoom("my-room-id");  // Process room} catch (error) {  if (error instanceof LiveblocksError) {    // Handle specific LiveblocksError cases    console.error(`Error fetching room: ${error.status} - ${error.message}`);    switch (      error.status      // Specific cases based on status codes    ) {    }  } else {    // Handle general errors    console.error(`Unexpected error: ${error.message}`);  }}

Utilities

getMentionedIdsFromCommentBody

Returns an array of each user’s ID that has been mentioned in a CommentBody (found under comment.body).

import { getMentionedIdsFromCommentBody } from "@liveblocks/node";
const mentionedIds = getMentionedIdsFromCommentBody(comment.body);

This is most commonly used in combination with the Comments API functions, for example getComment.

import { Liveblocks, getMentionedIdsFromCommentBody } from "@liveblocks/node";
// Create a node clientconst liveblocks = new Liveblocks({ secret: "",});
// Retrieve a commentconst comment = await liveblocks.getComment({ roomId: "my-room-id", threadId: "my-thread-id", commentId: "my-comment-id",});
// Get the mentions inside the comment's bodyconst mentionedIds = getMentionedIdsFromCommentBody(comment.body);
// ["marc@example.com", "vincent@example.com", ...]console.log(mentionedIds);

Here’s an example with a custom CommentBody.

import { CommentBody, getMentionedIdsFromCommentBody } from "@liveblocks/node";
// Create a custom `CommentBody`const commentBody: CommentBody = { version: 1, content: [ { type: "paragraph", children: [ { text: "Hello " }, { type: "mention", id: "chris@example.com" }, ], }, ],};
// Get the mentions inside the comment's bodyconst mentionedIds = getMentionedIdsFromCommentBody(commentBody);
// ["chris@example.com"]console.log(mentionedIds);

stringifyCommentBody

Used to convert a CommentBody (found under comment.body) into either a plain string, Markdown, HTML, or a custom format.

import { stringifyCommentBody } from "@liveblocks/node";
const stringComment = await stringifyCommentBody(comment.body);

This is most commonly used in combination with the Comments API functions, for example getComment.

import { Liveblocks, stringifyCommentBody } from "@liveblocks/node";
// Create a node clientconst liveblocks = new Liveblocks({ secret: "",});
// Retrieve a commentconst comment = await liveblocks.getComment({ roomId: "my-room-id", threadId: "my-thread-id", commentId: "my-comment-id",});
// Convert CommentBody to plain stringconst stringComment = await stringifyCommentBody(comment.body);
// "Hello marc@example.com from https://liveblocks.io"console.log(stringComment);

A number of options are also available.

import { stringifyCommentBody } from "@liveblocks/node";
const stringComment = await stringifyCommentBody(comment.body, { // Optional, convert to specific format, "plain" (default) | "markdown" | "html" format: "markdown",
// Optional, supply a separator to be used between paragraphs separator: `\n\n`,
// Optional, override any elements in the CommentBody with a custom string elements: { // Optional, override the `paragraph` element paragraph: ({ element, children }) => `<p>${children}</p>`,
// Optional, override the `text` element text: ({ element }) => element.bold ? `<strong>${element.text}</strong>` : `${element.text}`,
// Optional, override the `link` element link: ({ element, href }) => `<a href="${href}" target="_blank">${element.url}</a>`,
// Optional, override the `mention` element. `user` available if `resolveUsers` supplied mention: ({ element, user }) => `<a href="${user.profileUrl}">${element.id}</a>`, },
// Optional, get your user's names and info from their ID to be displayed in mentions async resolveUsers({ userIds }) { const usersData = await (userIds);
return usersData.map((userData) => ({ // Name is inserted into the output instead of a user's ID name: userData.name,
// Custom formatting in `elements.mention` allows custom properties to be used profileUrl: userData.profileUrl, })); },});

Formatting examples

Here are a number of different formatting examples derived from the same CommentBody.

// "Hello marc@example.com from https://liveblocks.io"await stringifyCommentBody(comment.body);
// "Hello @Marc from https://liveblocks.io"await stringifyCommentBody(comment.body, { resolveUsers({ userIds }) { return [{ name: "Marc" }]; },});
// "**Hello** @Marc from [https://liveblocks.io](https://liveblocks.io)"await stringifyCommentBody(comment.body, { format: "markdown",
resolveUsers() { return [{ name: "Marc" }]; },});
// "<b>Hello</b> <span data-mention>@Marc</span> from// <a href="https://liveblocks.io">https://liveblocks.io</a>"await stringifyCommentBody(comment.body, { format: "html",
resolveUsers() { return [{ name: "Marc" }]; },});
// "<b>Hello</b> <a href="https://example.com" data-id="marc@example.com">@Marc</a> from// <a href="https://liveblocks.io">https://liveblocks.io</a>"await stringifyCommentBody(comment.body, { format: "html",
mention: ({ element, user }) => `<a href="${user.profileUrl}" data-id="${element.id}">${user.name}</a>`,
resolveUsers() { return [{ name: "Marc", profileUrl: "https://example.com" }]; },});

WebhookHandler

The WebhookHandler class is a helper to handle webhook requests from Liveblocks.

It’s initialized with a signing secret that you can find in your project’s webhook page.

const webhookHandler = new WebhookHandler(process.env.WEBHOOK_SECRET);

verifyRequest

Verifies the request and returns the event. Note that rawBody takes the body as a string.

const event = webhookHandler.verifyRequest({  headers: req.headers,  rawBody: req.body,});

Some frameworks parse request bodies into objects, which means using JSON.stringify may be necessary.

const event = webhookHandler.verifyRequest({  headers: req.headers,  rawBody: JSON.stringify(req.body),});
Example using Next.js
import { WebhookHandler } from "@liveblocks/node";
// Will fail if not properly initialized with a secret// Obtained from the Webhooks section of your project dashboard// https://liveblocks.io/dashboardconst webhookHandler = new WebhookHandler(process.env.WEBHOOK_SECRET);
export function POST(request) { try { const event = webhookHandler.verifyRequest({ headers: req.headers, rawBody: JSON.stringify(req.body), });
// Handle `WebhookEvent`
if (event.type === "storageUpdated") { // Handle `StorageUpdatedEvent` } else if (event.type === "userEntered") { // Handle `UserEnteredEvent` } else if (event.type === "userLeft") { // Handle `UserLeftEvent` } } catch (error) { console.error(error); return new Response(error, { status: 400 }); }}

isThreadNotificationEvent

Type guard to check if a received webhook event is a ThreadNotificationEvent send from Comments. Particularly helpful when creating thread notification emails with webhooks.

import { isThreadNotificationEvent } from "@liveblocks/node";
const event = webhookHandler.verifyRequest({ headers: req.headers, rawBody: req.body,});
if (isThreadNotificationEvent(event)) { // Handle `ThreadNotificationEvent`}

The check is made against the event type and event data kind.

isTextMentionNotificationEvent

Type guard to check if a received webhook event is a TextMentionNotificationEvent sent from Text Editor. Particularly helpful for identifying text mentions when sending email notifications.

import { isTextMentionNotificationEvent } from "@liveblocks/node";
const event = webhookHandler.verifyRequest({ headers: req.headers, rawBody: req.body,});
if (isTextMentionNotificationEvent(event)) { // Handle `TextMentionNotificationEvent`}

The check is made against the event type and event data kind.

Deprecated

authorizeDeprecated

The purpose of authorize() was to help you implement your custom authentication back end. It generates old-style single-room tokens.

Please refer to our upgrade guide if you’re using the authorize function in your back end, and adopt Liveblocks.prepareSession or Liveblocks.identifyUser APIs instead.

We use cookies to collect data to improve your experience on our site. Read our Privacy Policy to learn more.