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 offers access to our REST API.
Sign in to get your own API keys.
import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: "sk_prod_xxxxxxxxxx…xxxxxxxxsk_prod_xxxxxxxxxxxxxxxxxxxxxxxx",});
Authentication
To authenticate your users with Liveblocks, you have the choice between two different APIs.
Liveblocks.identifyUserID token authentication is recommend for most applications.Liveblocks.prepareSessionAccess token authentication is best if you prefer handle permissions on your end, though it has limitations.
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",});
Learn how to get started with ID tokens.
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.
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
Sign in to get your own API keys.
import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: "sk_prod_xxxxxxxxxx…xxxxxxxxsk_prod_xxxxxxxxxxxxxxxxxxxxxxxx",});
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 = __getUserFromDB__(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");
Learn how to get started with access\ tokens.
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
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", session.FULL_ACCESS);
// Giving read-only access to this roomsession.allow("room-id-2", session.READ_ACCESS);
// Giving access to multiple rooms with a wildcard// `design-room-1`, `design-room-2`, etc.session.allow("design-room:*", session.FULL_ACCESS);
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
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", // ... }, });
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.
To implement your back end, follow these steps:
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.
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);
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.
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
Sign in to get your own API keys.
import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: "sk_prod_xxxxxxxxxx…xxxxxxxxsk_prod_xxxxxxxxxxxxxxxxxxxxxxxx",});
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 = __getUserFromDB__(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 && __shouldUserHaveAccess__(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 });}
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);}
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 ["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", 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, `["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", },
// 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
["room: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: ["room: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, `["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", },
// Optional, create it on a specific organization organizationId: "acme-corp",});
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, `["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", },});
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
["room: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: ["room: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, `["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", }, },});
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:
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
Sign in to get your own API keys.
"use server";
import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: "sk_prod_xxxxxxxxxx…xxxxxxxxsk_prod_xxxxxxxxxxxxxxxxxxxxxxxx",});
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; __redirect__(`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: ["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.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");
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:
Slate binary update
Sign in to get your own API keys.
Create a binary update with Tiptap:
Tiptap binary update
Sign in to get your own API keys.
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.
Attachments
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 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(),
// Optional, custom comment metadata metadata: { tag: "important", spam: false, },
// 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,
// ... },});
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, }, },});
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: { userId: "alicia@example.com", 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, }, },});
// { type: "comment", threadId: "th_d75sF3...", ... }console.log(editedComment);
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,
// ... },});
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:
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.
await liveblocks.updateFeedMessage({ roomId: "my-room-id", feedId: "my-feed-id", messageId: "my-message-id",
// The message data data: { role: "user", content: "Updated content", },
// Optional, timestamp in milliseconds. Helps prevents race conditions, // messages that arrive after a newer message are ignored updatedAt: Date.now(),});
Liveblocks.deleteFeedMessage
Deletes a feed message. This is a wrapper around the Delete Feed Message API.
await liveblocks.deleteFeedMessage({ roomId: "my-room-id", feedId: "my-feed-id", messageId: "my-message-id",});
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, nextCursor } = 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, nextCursor } = await liveblocks.getInboxNotifications({ userId: "steven@example.com", query: { unread: true },
// Optional, filter for a specific organization organizationId: "acme-corp", });
Pagination
You can use nextCursor to paginate inbox notifications. In this example, when
getNextPage is called, the next page of inbox notifications is added to
pages.
import { InboxNotificationData } from "@liveblocks/node";
// An array of pages, each containing a list of retrieved inbox notificationsconst pages: InboxNotificationData[][] = [];
// Holds the pagination cursor for the next set of inbox notificationslet startingAfter;
// Call to get the next page of inbox notificationsasync function getNextPage() { const { data, nextCursor } = await liveblocks.getInboxNotifications({ startingAfter, }); pages.push(data); startingAfter = nextCursor;}
If you’d like to iterate over all your inbox notifications, it’s most convenient
to use
liveblocks.iterInboxNotifications
instead. This method automatically paginates your API requests.
Liveblocks.iterInboxNotifications
Returns a list of inbox notifications for the given user. Works similarly to
liveblocks.getInboxNotifications, but
instead returns an asynchronous iterator, which helps you iterate over all the
inbox notifications, without having to manually paginate through the results.
const userId = "steven@example.com";
for await (const item of liveblocks.iterInboxNotifications({ userId, // Optional, filter for a specific organization organizationId: "acme-corp",})) { console.log(item.id); // in_3dH7sF3... console.log(item.kind); // "thread", "textMention", ...}
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. Notifications can be batched. 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",
// Optional, trigger it for a specific organization organizationId: "acme-corp",});
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 // ... }}
Batching custom notifications
You can configure a custom notification kind to have batching enabled. When it’s
enabled, triggering an inbox notification activity for a specific subjectId,
will update the existing inbox notification instead of creating a new one.
To use this, you must first
enable batching in the dashboard.
Next, trigger a notification with the same subjectId as an existing
notification, and the result will be added to the activityData array.
const options = { userId: "steven@example.com", kind: "$fileUploaded", subjectId: "my-file",};
await liveblocks.triggerInboxNotification({ ...options,
activityData: { status: "processing", },});
await liveblocks.triggerInboxNotification({ ...options,
activityData: { status: "complete", },});
const { data: inboxNotifications } = await liveblocks.getInboxNotifications({ userId: "steven@example.com",});
// {// id: "in_3dH7sF3...",// kind: "$fileUploaded",// activities: [// { status: "processing" },// { status: "complete" },// ],// ...// }console.log(inboxNotifications[0]);
An inbox notification can have up to 50 activities, if you exceed this number, a new inbox notification will be created.
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", // Optional, delete for a specific organization organizationId: "acme-corp",});
Liveblocks.getNotificationSettingsBeta
Returns a user’s notification settings in the current project, in other words which notification webhook events will be sent for the user. Notification settings are project-based, which means that this returns the user’s settings for every room. This a wrapper around the Get Notification Settings API.
const settings = await liveblocks.getNotificationSettings({ userId: "guillaume@liveblocks.io",});
// { email: { thread: true, ... }, slack: { thread: false, ... }, ... }console.log(settings);
A user’s initial settings are set in the dashboard, and different kinds should
be enabled there. If no kind is enabled on the current channel, null will be
returned. For example, with the email channel:
const settings = await liveblocks.getNotificationSettings({ userId: "guillaume@liveblocks.io",});
// { email: null, ... }console.log(settings);
Liveblocks.updateNotificationSettingsBeta
Updates a user’s notification settings, which affects which
notification webhook events will be
sent for the user. Notification settings are project-based, which means that
this modifies the user’s settings in every room. Each notification kind must
first be enabled on your project’s notification dashboard page before settings
can be used. This a wrapper around the
Update Notification Settings API.
const updatedSettings = await liveblocks.updateNotificationSettings({ userId: "steven@example.com", data: { email: { thread: false }, slack: { textMention: true }, },});
// { email: { thread: false, ... }, slack: { textMention: true, ... }, ... }console.log(updatedSettings);
You can pass a partial object, or many settings at once.
// You only need to pass partialsawait liveblocks.updateNotificationSettings({ userId: "steven@example.com", email: { thread: true },});
// Enabling a custom notification on the slack channelawait liveblocks.updateNotificationSettings({ userId: "steven@example.com", slack: { $myCustomNotification: true },});
// Setting complex settingsawait liveblocks.updateNotificationSettings({ userId: "steven@example.com", email: { thread: true, textMention: false, $newDocument: true, }, slack: { thread: false, $fileUpload: false, }, teams: { thread: true, },});
Liveblocks.deleteNotificationSettingsBeta
Deletes the user’s notification settings, resetting them to the default values. The default values can be adjusted in a project’s notification dashboard page. This a wrapper around the Delete Notification Settings API.
await liveblocks.deleteNotificationSettings({ userId: "adri@example.com",});
AI Copilots
Liveblocks.getAiCopilots
Returns a paginated list of AI copilots. The copilots are returned sorted by creation date, from newest to oldest. This is a wrapper around the Get AI Copilots API and returns the same response.
const { data: copilots, nextCursor } = await liveblocks.getAiCopilots();
// A list of AI copilots// [{ type: "copilot", id: "co_abc123...", name: "My Copilot", ... }, ...]console.log(copilots);
// A pagination cursor used for retrieving the next page of results with `startingAfter`// "L3YyL3Jvb21z..."console.log(nextCursor);
Pagination options are available to control the number of results returned.
const { data: copilots, nextCursor } = await liveblocks.getAiCopilots({ // Optional, the amount of copilots to load, between 1 and 100, defaults to 20 limit: 20,
// Optional, cursor used for pagination, use `nextCursor` from the previous page's response startingAfter: "L3YyL3Jvb21z...",});
Liveblocks.createAiCopilot
Creates a new AI copilot with the given configuration. This is a wrapper around the Create AI Copilot API and returns the same response.
const copilot = await liveblocks.createAiCopilot({ name: "My AI Assistant", systemPrompt: "You are a helpful AI assistant for our team.", provider: "openai", providerModel: "gpt-4", providerApiKey: "sk-...", // Your OpenAI API key});
// { type: "copilot", id: "co_abc123...", name: "My AI Assistant", ... }console.log(copilot);
The method supports various configuration options for different AI providers.
const copilot = await liveblocks.createAiCopilot({ // Required, the name of the copilot name: "Documentation Helper",
// Optional, a description of what the copilot does description: "Helps users understand our documentation",
// Required, the system prompt that defines the copilot's behavior systemPrompt: "You are an expert at helping users understand technical documentation.",
// Optional, additional knowledge context for the copilot knowledgePrompt: "Use our company's style guide when providing examples.",
// Optional, always retrieve knowledge sources on each query alwaysUseKnowledge: true,
// Required, the AI provider to use provider: "openai",
// Required for standard providers, the model to use providerModel: "gpt-4-turbo",
// Required, your API key for the provider providerApiKey: "sk-...",
// Optional, provider-specific options providerOptions: { openai: { reasoningEffort: "low", // Optional, restrict web search to specific domains for OpenAI webSearch: { allowedDomains: ["docs.liveblocks.io", "example.com"], }, }, },
// Optional, model settings settings: { maxTokens: 1000, temperature: 0.7, topP: 0.9, frequencyPenalty: 0.1, presencePenalty: 0.1, stopSequences: ["END"], seed: 42, maxRetries: 3, },});
For OpenAI-compatible providers, use a different configuration:
const copilot = await liveblocks.createAiCopilot({ name: "Custom AI Helper", systemPrompt: "You are a helpful assistant.", provider: "openai-compatible", compatibleProviderName: "my-custom-provider", providerBaseUrl: "https://api.mycustomprovider.com/v1", providerApiKey: "your-api-key-here", // Your API key for the custom provider providerModel: "custom-provider-model",});
You can also configure Anthropic or Google providers with provider-specific options:
// Anthropic exampleawait liveblocks.createAiCopilot({ name: "Anthropic Helper", systemPrompt: "You are a helpful assistant.", provider: "anthropic", providerModel: "claude-3-5-sonnet-latest", providerApiKey: "sk-...", providerOptions: { anthropic: { thinking: { type: "disabled" }, webSearch: { allowedDomains: ["example.com"], }, }, },});
// Google exampleawait liveblocks.createAiCopilot({ name: "Gemini Helper", systemPrompt: "You are a helpful assistant.", provider: "google", providerModel: "gemini-2.5-pro", providerApiKey: "sk-...", providerOptions: { google: { thinkingConfig: { thinkingBudget: 2000 }, }, },});
Liveblocks.getAiCopilot
Returns an AI copilot by its ID. Throws an error if the copilot isn’t found. This is a wrapper around the Get AI Copilot API and returns the same response.
const copilot = await liveblocks.getAiCopilot("co_abc123...");
// { type: "copilot", id: "co_abc123...", name: "My AI Assistant", ... }console.log(copilot);
Liveblocks.updateAiCopilot
Updates an existing AI copilot’s configuration. You only need to pass the properties you want to update. Throws an error if the copilot isn’t found. This is a wrapper around the Update AI Copilot API and returns the same response.
const updatedCopilot = await liveblocks.updateAiCopilot("co_abc123...", { name: "Updated AI Assistant", description: "Now with improved capabilities",});
// { type: "copilot", id: "co_abc123...", name: "Updated AI Assistant", ... }console.log(updatedCopilot);
You can update various aspects of the copilot:
const updatedCopilot = await liveblocks.updateAiCopilot("co_abc123...", { // Optional, update the name name: "Better AI Helper",
// Optional, update the description description: "Enhanced with new features",
// Optional, update the system prompt systemPrompt: "You are an even more helpful AI assistant.",
// Optional, update the knowledge prompt knowledgePrompt: "Reference our latest guidelines.",
// Optional, always retrieve knowledge sources on each query alwaysUseKnowledge: true,
// Optional, update provider-specific options (replaces the entire nested options object) // Set to null to clear options providerOptions: null,
// Optional, update model settings settings: { temperature: 0.5, maxTokens: 1500, },});
You can also update provider options. When updating providerOptions, it fully
replaces the previous nested options (no deep merge):
// Update OpenAI provider options (replaces prior options)await liveblocks.updateAiCopilot("co_abc123...", { provider: "openai", providerOptions: { openai: { reasoningEffort: "medium", webSearch: { allowedDomains: ["docs.liveblocks.io"] }, }, },});
// Update Anthropic thinking/web search settingsawait liveblocks.updateAiCopilot("co_abc123...", { provider: "anthropic", providerOptions: { anthropic: { thinking: { type: "enabled", budgetTokens: 2000 }, webSearch: { maxUses: 2 }, }, },});
Certain properties can be set to null to clear them from the copilot’s
configuration. This includes description, knowledgePrompt, settings, and
providerOptions.
const updatedCopilot = await liveblocks.updateAiCopilot("co_abc123...", { // Clear the description description: null,
// Clear the knowledge prompt knowledgePrompt: null,
// Clear all model settings settings: null,});
The method returns a 422 response if the update doesn’t apply due to validation failures. For example, if the existing copilot uses the "openai" provider and you attempt to update the provider model to an incompatible value for the provider, like "gemini-2.5-pro", you’ll receive a 422 response with an error message explaining where the validation failed.
Liveblocks.deleteAiCopilot
Deletes an AI copilot by its ID. A deleted copilot is no longer accessible and cannot be restored. Throws an error if the copilot isn’t found. This is a wrapper around the Delete AI Copilot API and returns no response.
await liveblocks.deleteAiCopilot("co_abc123...");
Knowledge Sources
Liveblocks.createWebKnowledgeSource
Creates a web knowledge source for an AI copilot. This allows the copilot to access and learn from web content. This is a wrapper around the Create Web Knowledge Source API and returns the ID of the created knowledge source.
const { id } = await liveblocks.createWebKnowledgeSource({ copilotId: "co_abc123...", url: "https://example.com/documentation", type: "individual_link",});
// "ks_def456..."console.log(id);
Different types of web knowledge sources are supported:
// Index a single web pageconst singlePage = await liveblocks.createWebKnowledgeSource({ copilotId: "co_abc123...", url: "https://example.com/important-page", type: "individual_link",});
// Crawl an entire websiteconst crawledSite = await liveblocks.createWebKnowledgeSource({ copilotId: "co_abc123...", url: "https://example.com", type: "crawl",});
// Use a sitemap to index multiple pagesconst sitemapSource = await liveblocks.createWebKnowledgeSource({ copilotId: "co_abc123...", url: "https://example.com/sitemap.xml", type: "sitemap",});
Liveblocks.createFileKnowledgeSource
Creates a file knowledge source for an AI copilot by uploading a file. The copilot can then reference the content of the file when responding. This is a wrapper around the Create File Knowledge Source API and returns the ID of the created knowledge source.
Note: Currently only PDF files (application/pdf) and images (image/*)
are supported.
const { id } = await liveblocks.createFileKnowledgeSource({ copilotId: "co_abc123...", file: pdfFile, // Must be a PDF or image file});
// "ks_ghi789..."console.log(id);
Liveblocks.getKnowledgeSources
Returns a paginated list of knowledge sources for a specific AI copilot. This is a wrapper around the Get Knowledge Sources API and returns the same response.
const { data: sources, nextCursor } = await liveblocks.getKnowledgeSources({ copilotId: "co_abc123...",});
// [{ type: "ai-knowledge-web-source", id: "ks_abc123...", ... }, ...]console.log(sources);
Pagination options are available:
const { data: sources, nextCursor } = await liveblocks.getKnowledgeSources({ copilotId: "co_abc123...", // Optional, the amount of knowledge sources to load, between 1 and 100, defaults to 20 limit: 20, // Optional, cursor used for pagination startingAfter: "L3YyL3Jvb21z...",});
Liveblocks.getKnowledgeSource
Returns a specific knowledge source by its ID. Throws an error if the knowledge source isn’t found. This is a wrapper around the Get Knowledge Source API and returns the same response.
const source = await liveblocks.getKnowledgeSource({ copilotId: "co_abc123...", knowledgeSourceId: "ks_def456...",});
// { type: "ai-knowledge-web-source", id: "ks_def456...", ... }// or { type: "ai-knowledge-file-source", id: "ks_def456...", ... }console.log(source);
Liveblocks.getFileKnowledgeSourceMarkdown
Returns the content of a file knowledge source as Markdown. This allows you to see what content the AI copilot has access to from uploaded files. Throws an error if the knowledge source isn’t found. This is a wrapper around the Get File Knowledge Source Content API and returns the content as a string.
const content = await liveblocks.getFileKnowledgeSourceMarkdown({ copilotId: "co_abc123...", knowledgeSourceId: "ks_def456...",});
// "# Document Title\n\nThis is the content of the uploaded file..."console.log(content);
Liveblocks.getWebKnowledgeSourceLinks
Returns a paginated list of links that were indexed from a web knowledge source. This is useful for understanding what content the AI copilot has access to from web sources. This is a wrapper around the Get Web Knowledge Source Links API and returns the same response.
const { data: links, nextCursor } = await liveblocks.getWebKnowledgeSourceLinks( { copilotId: "co_abc123...", knowledgeSourceId: "ks_def456...", });
// [{ id: "link_123...", url: "https://example.com/page1", status: "ready", ... }, ...]console.log(links);
Pagination options are available:
const { data: links, nextCursor } = await liveblocks.getWebKnowledgeSourceLinks( { copilotId: "co_abc123...", knowledgeSourceId: "ks_def456...", // Optional, the amount of links to load, between 1 and 100, defaults to 20 limit: 20, // Optional, cursor used for pagination startingAfter: "L3YyL3Jvb21z...", });
Liveblocks.deleteWebKnowledgeSource
Deletes a web knowledge source from an AI copilot. The copilot will no longer have access to the content from this source. Throws an error if the knowledge source isn’t found. This is a wrapper around the Delete Web Knowledge Source API and returns no response.
await liveblocks.deleteWebKnowledgeSource({ copilotId: "co_abc123...", knowledgeSourceId: "ks_def456...",});
Liveblocks.deleteFileKnowledgeSource
Deletes a file knowledge source from an AI copilot. The copilot will no longer have access to the content from this file. Throws an error if the knowledge source isn’t found. This is a wrapper around the Delete File Knowledge Source API and returns no response.
await liveblocks.deleteFileKnowledgeSource({ copilotId: "co_abc123...", knowledgeSourceId: "ks_def456...",});
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
getMentionsFromCommentBody
Returns an array of mentions from a CommentBody (found under comment.body).
import { getMentionsFromCommentBody } from "@liveblocks/node";
const mentions = getMentionsFromCommentBody(comment.body);
An optional second argument can be used to filter the returned mentions. By default, if it’s not provided, all mentions are returned, including future mention kinds (e.g. group mentions in the future).
// All mentions (same as `getMentionsFromCommentBody(commentBody)`)getMentionsFromCommentBody(commentBody);
// Only user mentions with an ID of "123"getMentionsFromCommentBody( commentBody, (mention) => mention.kind === "user" && mention.id === "123");
// Only mentions with an ID which starts with "prefix:"getMentionsFromCommentBody(commentBody, (mention) => ( mention.id.startsWith("prefix:"));
This is most commonly used in combination with the
Comments API functions, for
example getComment.
Sign in to get your own API keys.
import { Liveblocks, getMentionsFromCommentBody } from "@liveblocks/node";
// Create a node clientconst liveblocks = new Liveblocks({ secret: "sk_prod_xxxxxxxxxx…xxxxxxxxsk_prod_xxxxxxxxxxxxxxxxxxxxxxxx",});
// 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 mentions = getMentionsFromCommentBody(comment.body);
// [{ kind: "user", id: "marc@example.com" }, { kind: "user", id: "vincent@example.com" }, ...]console.log(mentions);
Here’s an example with a custom CommentBody.
import { CommentBody, getMentionsFromCommentBody } 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 mentions = getMentionsFromCommentBody(commentBody);
// [{ kind: "user", id: "chris@example.com" }]console.log(mentions);
Also available from @liveblocks/client
If you’d like to use this on the client side, it’s also available from
@liveblocks/client.
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);
Sign in to get your own API keys.
import { Liveblocks, stringifyCommentBody } from "@liveblocks/node";
// Create a node clientconst liveblocks = new Liveblocks({ secret: "sk_prod_xxxxxxxxxx…xxxxxxxxsk_prod_xxxxxxxxxxxxxxxxxxxxxxxx",});
// 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/client";
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` and `group` are the optional data returned from `resolveUsers` and `resolveGroupsInfo` mention: ({ element, user, group }) => `<a href="${user?.profileUrl ?? group?.settingsUrl ?? "#"}">${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 __getUsersFromDB__(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, })); },
// Optional, get your group’s names and info from their ID to be displayed in mentions async resolveGroupsInfo({ groupIds }) { const groupsData = await __getGroupsFromDB__(groupIds);
return groupsData.map((groupData) => ({ // Name is inserted into the output instead of a group’s ID name: groupData.name,
// Custom formatting in `elements.mention` allows custom properties to be used settingsUrl: groupData.settingsUrl, })); },});
Also available from @liveblocks/client
If you’d like to use this on the client side, it’s also available from
@liveblocks/client.
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](/content/site-root.html)"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
Need help implementing webhooks?
Read the Webhooks guide to learn how to use them within your product, allowing you to react to Liveblocks events as they happen.
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`}
isCustomNotificationEvent
Type guard to check if a received webhook event is a
CustomNotificationEvent sent
from
triggerInboxNotification.
Particularly helpful for identifying custom notifications when sending email
notifications.
import { isCustomNotificationEvent } from "@liveblocks/node";
const event = webhookHandler.verifyRequest({ headers: req.headers, rawBody: req.body,});
if (isCustomNotificationEvent(event)) { // Handle `CustomNotificationEvent`}
The check is made against the event type and event data kind.