# API Reference \- @liveblocks/node

`@liveblocks/node` provides you with Node.js APIs for
[authenticating Liveblocks users](/content/docs/api-reference/liveblocks-node#Liveblocks-client/index.html) and for
[implementing webhook handlers](/content/docs/api-reference/liveblocks-node#WebhookHandler/index.html). This library is only intended
for use in your Node.js back end.

## [Liveblocks client](/content/docs/api-reference/liveblocks-node\#Liveblocks-client/index.html)

The `Liveblocks` client offers access to our REST API.

[Sign in](/content/dashboard/apikeys/index.html) to get your own API keys.

```ts
import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({  secret: "sk_prod_xxxxxxxxxx…xxxxxxxxsk_prod_xxxxxxxxxxxxxxxxxxxxxxxx",});
```

### [Authentication](/content/docs/api-reference/liveblocks-node\#Authorization/index.html)

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

- [`Liveblocks.identifyUser`](/content/docs/api-reference/liveblocks-node#id-tokens/index.html) ID token authentication is recommend
for most applications.
- [`Liveblocks.prepareSession`](/content/docs/api-reference/liveblocks-node#access-tokens/index.html) Access token authentication is
best if you prefer handle permissions on your end, though it has
[limitations](/content/docs/authentication/access-token#limitations/index.html).

#### [Liveblocks.identifyUser](/content/docs/api-reference/liveblocks-node\#id-tokens/index.html)

Creates an ID token that is used to authenticate a user in your application.
This is a wrapper around the
[Get ID Token API](/content/docs/api-reference/rest-api-endpoints#post-identify-user/index.html)
and returns the same response.

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

Learn how to [get started with ID tokens](/content/docs/authentication/index.html).

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

```ts
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](/content/docs/api-reference/liveblocks-node\#Granting-ID-token-permissions/index.html)

You can pass additional options to `identifyUser`, enabling you to create
complex [workspace permissions](/content/docs/authentication#permissions/index.html) and
[room permissions](/content/docs/authentication#Room-permissions/index.html). For example, this
user can only see resources in the `acme-corp` workspace, and they’re part of a
`marketing` rooms group within it.

```ts
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](/content/docs/authentication#permissions/index.html).

##### [Text editor user data](/content/docs/api-reference/liveblocks-node\#Text-editor-user-data/index.html)

When using
[text editor integrations](/content/docs/ready-made-features/multiplayer#Text-editor-integrations/index.html),
user data is inserted into their live cursor within the editor, showing their
name and color. This data originates from the `userInfo` property.

```ts
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](/content/docs/api-reference/liveblocks-node\#Custom-user-metadata/index.html)

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.

```ts
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`](/content/docs/api-reference/liveblocks-react#useSelf/index.html) and
[`useOthers`](/content/docs/api-reference/liveblocks-react#useOthers/index.html).

```tsx
const currentUser = useSelf();
// "https://example.com/avatar/marie.jpg"console.log(currentUser.info.avatar);
```

##### [How ID tokens work](/content/docs/api-reference/liveblocks-node\#How-ID-tokens-work/index.html)

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](/content/docs/authentication/id-token/index.html) 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](/content/docs/authentication/id-token/index.html) 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:

```ts
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](/content/docs/authentication/id-token/index.html) 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](/content/docs/authentication/id-token/index.html) 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](/content/docs/api-reference/liveblocks-node\#ID-tokens-example/index.html)

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](/content/docs/authentication/id-token/index.html).

Next.js

[Sign in](/content/dashboard/apikeys/index.html) to get your own API keys.

```ts
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](/content/docs/api-reference/liveblocks-node\#access-tokens/index.html)

Creates an access token that is used to authenticate a user in your application.
This is a wrapper around the
[Get Access Token API](/content/docs/api-reference/rest-api-endpoints#post-authorize-user/index.html)
and returns the same response.

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

Learn how to [get started with access\\
tokens](/content/docs/authentication/access-token/index.html).

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

```ts
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](/content/docs/api-reference/liveblocks-node\#Granting-access-token-permissions/index.html)

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](/content/docs/authentication/access-token#Naming-pattern/index.html).

```ts
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](/content/docs/authentication/access-token#permissions/index.html).

Additionally, you can pass additional options to `prepareSession`, enabling you
to create complex permissions using
[organizations](/content/docs/authentication/organizations/index.html) and
[accesses](/content/docs/authentication/access-tokens/permissions/index.html). For example, this
user can only see resources in the `acme-corp` organization, and they're part of
a `marketing` group within it.

```ts
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](/content/docs/api-reference/liveblocks-node\#Text-editor-user-data/index.html)

When using
[text editor integrations](/content/docs/ready-made-features/multiplayer#Text-editor-integrations/index.html),
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.

```ts
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](/content/docs/api-reference/liveblocks-node\#Custom-user-metadata/index.html)

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

```tsx
const currentUser = useSelf();
// "https://example.com/avatar/marie.jpg"console.log(currentUser.info.avatar);
```

##### [How access tokens work](/content/docs/api-reference/liveblocks-node\#How-access-tokens-work/index.html)

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](/content/docs/authentication/access-token/index.html) 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:

1. ### Create a session

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

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

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

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

2. ### Decide which permissions to allow this session

```ts
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.

3. ### Authorize the session

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

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

##### [Access tokens example](/content/docs/api-reference/liveblocks-node\#access-token-example/index.html)

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](/content/docs/authentication/access-token/index.html).

route.ts

[Sign in](/content/dashboard/apikeys/index.html) to get your own API keys.

```ts
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](/content/docs/api-reference/liveblocks-node\#Room/index.html)

#### [Liveblocks.getRooms](/content/docs/api-reference/liveblocks-node\#get-rooms/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-rooms/index.html) and returns
the same response.

```ts
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.

```ts
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](/content/docs/guides/how-to-filter-rooms-using-query-language/index.html) string
instead of a `query` object.

##### [Pagination](/content/docs/api-reference/liveblocks-node\#Pagination/index.html)

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

```ts
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`](/content/docs/api-reference/liveblocks-node#iter-rooms/index.html) instead. This method automatically
paginates your API requests.

#### [Liveblocks.iterRooms](/content/docs/api-reference/liveblocks-node\#iter-rooms/index.html)

Works similarly to [`liveblocks.getRooms`](/content/docs/api-reference/liveblocks-node#get-rooms/index.html), 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.

```ts
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.

```ts
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](/content/docs/api-reference/liveblocks-node\#Mass-deleting-rooms/index.html)

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:

```ts
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](/content/docs/api-reference/liveblocks-node\#post-rooms/index.html)

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](/content/docs/authentication/id-token/index.html). This is a wrapper
around the [Create Room API](/content/docs/api-reference/rest-api-endpoints#post-rooms/index.html)
and returns the same response.

```ts
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.

```ts
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](/content/docs/api-reference/liveblocks-node#id-tokens/index.html), learn
more about [managing permission with ID tokens](/content/docs/authentication/id-token/index.html).

#### [Liveblocks.getRoom](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId/index.html)

Returns a room. Throws an error if the room isn’t found. This is a wrapper
around the
[Get Room API](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId/index.html) and
returns the same response.

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

#### [Liveblocks.getOrCreateRoom](/content/docs/api-reference/liveblocks-node\#get-or-create-rooms-roomId/index.html)

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](/content/docs/authentication/id-token/index.html). Returns the same
response as the
[Create Room API](/content/docs/api-reference/rest-api-endpoints#post-rooms/index.html).

```ts
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.

```ts
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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId/index.html)

Updates properties on a room. Throws an error if the room isn’t found. This is a
wrapper around the
[Update Room API](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId/index.html) and
returns the same response.

```ts
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.

```ts
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](/content/docs/api-reference/liveblocks-node\#upsert-rooms-roomId/index.html)

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](/content/docs/authentication/id-token/index.html). Returns the same
response as the
[Create Room API](/content/docs/api-reference/rest-api-endpoints#post-rooms/index.html).

```ts
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.

```ts
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](/content/docs/api-reference/liveblocks-node\#delete-rooms-roomId/index.html)

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](/content/docs/api-reference/rest-api-endpoints#delete-rooms-roomId/index.html)
and returns no response.

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

##### [Mass deleting rooms](/content/docs/api-reference/liveblocks-node\#Mass-deleting-rooms/index.html)

If you need to delete multiple rooms at once, you can use
[`liveblocks.iterRooms`](/content/docs/api-reference/liveblocks-node#iter-rooms/index.html) 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`](/content/docs/api-reference/liveblocks-node#iter-rooms/index.html) to select which rooms to delete, such as
filtering by metadata, room ID prefix, or user/group access.

#### [Liveblocks.prewarmRoom](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-prewarm/index.html)

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`](/content/docs/api-reference/liveblocks-react#RoomProvider/index.html) or
[`enterRoom`](/content/docs/api-reference/liveblocks-client#Client.enterRoom) within 10
seconds, and the room will load quicker. This is a wrapper around the
[Prewarm Room API](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-prewarm/index.html)
and returns no response.

```ts
await liveblocks.prewarmRoom("my-room-id");
```

##### [Warm a room before navigating](/content/docs/api-reference/liveblocks-node\#Warm-a-room-before-navigating/index.html)

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](/content/dashboard/apikeys/index.html) to get your own API keys.

```ts
"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

```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](/content/docs/api-reference/liveblocks-node\#post-rooms-update-roomId/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-update-roomId/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node\#Redirect-connected-users-to-the-new-room/index.html)

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`](/content/docs/api-reference/liveblocks-react#useErrorListener/index.html) or
[`room.subscribe("error")`](/content/docs/api-reference/liveblocks-client#Room.subscribe.error)
in your application to get the new room’s ID, and redirect users to the renamed
room.

```tsx
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](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-active-users/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-active-users/index.html)
and returns the same response.

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

#### [Liveblocks.broadcastEvent](/content/docs/api-reference/liveblocks-node\#post-broadcast-event/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-broadcast-event/index.html)
and returns no response.

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

You can respond to custom events on the front end with
[`useEventListener`](/content/docs/api-reference/liveblocks-react#useEventListener/index.html) and
[`room.subscribe("event")`](/content/docs/api-reference/liveblocks-client#Room.subscribe.event).
When receiving an event sent with `Liveblocks.broadcastEvent`, `user` will be
`null` and `connectionId` will be `-1`.

```tsx
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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-presence/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-presence/index.html)
and returns no response on success.

```ts
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](/content/docs/api-reference/liveblocks-node\#Groups/index.html)

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

#### [Liveblocks.createGroup](/content/docs/api-reference/liveblocks-node\#create-group/index.html)

Creates a new group with the specified members. This is a wrapper around the
[Create Group API](/content/docs/api-reference/rest-api-endpoints#post-groups/index.html) and
returns the same response.

```ts
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:

```ts
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](/content/docs/api-reference/liveblocks-node\#get-group/index.html)

Returns a group by its ID. Throws an error if the group isn’t found. This is a
wrapper around the
[Get Group API](/content/docs/api-reference/rest-api-endpoints#get-groups-groupId/index.html) and
returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node\#get-groups/index.html)

Returns a list of all groups in your project. This is a wrapper around the
[Get Groups API](/content/docs/api-reference/rest-api-endpoints#get-groups/index.html) and returns
the same response.

```ts
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:

```ts
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](/content/docs/api-reference/liveblocks-node\#get-user-groups/index.html)

Returns all groups that a specific user is a member of. This is a wrapper around
the
[Get User Groups API](/content/docs/api-reference/rest-api-endpoints#get-users-userId-groups/index.html)
and returns the same response.

```ts
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:

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

#### [Liveblocks.addGroupMembers](/content/docs/api-reference/liveblocks-node\#add-group-members/index.html)

Adds new members to an existing group. This is a wrapper around the
[Add Group Members API](/content/docs/api-reference/rest-api-endpoints#post-groups-groupId-add-members/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node\#remove-group-members/index.html)

Removes members from an existing group. This is a wrapper around the
[Remove Group Members API](/content/docs/api-reference/rest-api-endpoints#post-groups-groupId-remove-members/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node\#delete-group/index.html)

Deletes a group. If the group doesn’t exist, no error will be thrown. This is a
wrapper around the
[Delete Group API](/content/docs/api-reference/rest-api-endpoints#delete-groups-groupId/index.html)
and returns no response.

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

### [Storage](/content/docs/api-reference/liveblocks-node\#Storage/index.html)

#### [Liveblocks.getStorageDocument](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-storage/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-storage/index.html)
and returns the same response.

```ts
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.

```ts
// 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.

```ts
// 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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-storage/index.html)

Initializes a room’s Storage tree with given LSON data. To use this, the room
must have [already been created](/content/docs/api-reference/liveblocks-node#post-rooms/index.html) 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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-storage/index.html)
and returns the same response.

```ts
// 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`.

```ts
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.

```ts
// 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](/content/docs/api-reference/liveblocks-node\#mutate-storage/index.html)

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

```ts
// 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.

```ts
// 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](/content/docs/api-reference/liveblocks-react#Typing-your-data/index.html).

#### [Liveblocks.massMutateStorage](/content/docs/api-reference/liveblocks-node\#mass-mutate-storage/index.html)

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

```ts
// 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.

```ts
// 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](/content/docs/api-reference/liveblocks-react#Typing-your-data/index.html).

#### [Liveblocks.deleteStorageDocument](/content/docs/api-reference/liveblocks-node\#delete-rooms-roomId-storage/index.html)

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](/content/docs/api-reference/rest-api-endpoints#delete-rooms-roomId-storage/index.html)
and returns no response.

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

### [Yjs](/content/docs/api-reference/liveblocks-node\#Yjs/index.html)

#### [Liveblocks.getYjsDocument](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-ydoc/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-ydoc/index.html)
and returns the same response.

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

A number of options are available.

```ts
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](/content/docs/api-reference/liveblocks-node\#put-rooms-roomId-ydoc/index.html)

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](/content/docs/api-reference/rest-api-endpoints#put-rooms-roomId-ydoc/index.html)
and returns no response.

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

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

```ts
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`.

```ts
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`](/content/docs/api-reference/liveblocks-node#post-rooms/index.html) before sending the binary update.

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

##### [Different editors](/content/docs/api-reference/liveblocks-node\#Different-editors/index.html)

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`](https://docs.yjs.dev/api/shared-types/y.xmlfragment) instead
of [`Y.Text`](https://docs.yjs.dev/api/shared-types/y.text).

Create a binary update with [Slate](https://www.slatejs.org/):

Slate binary update

[Sign in](/content/dashboard/apikeys/index.html) to get your own API keys.

Create a binary update with
[Tiptap](https://tiptap.dev/docs/editor/api/extensions/collaboration):

Tiptap binary update

[Sign in](/content/dashboard/apikeys/index.html) to get your own API keys.

Read the [Yjs documentation](https://docs.yjs.dev/api/document-updates) to learn
more about creating binary updates.

#### [Liveblocks.getYjsDocumentAsBinaryUpdate](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-ydoc-binary/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-ydoc-binary/index.html)
and returns the same response.

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

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

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

Read the [Yjs documentation](https://docs.yjs.dev/api/document-updates) to learn
more about using binary updates.

### [Attachments](/content/docs/api-reference/liveblocks-node\#Attachments/index.html)

#### [Liveblocks.getAttachment](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-attachments-attachmentId/index.html)

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

```ts
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](/content/docs/api-reference/liveblocks-node\#Comments/index.html)

#### [Liveblocks.getThreads](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-threads/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-threads/index.html)
and returns the same response.

```ts
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.

```ts
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](/content/docs/guides/how-to-filter-threads-using-query-language/index.html) string
instead of a `query` object.

#### [Liveblocks.createThread](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-threads/index.html)

Creates a new thread within a specific room, using room ID and thread data. This
is a wrapper around the
[Create Thread API](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads/index.html)
and returns the new thread.

```ts
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`.

```tsx
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.

```ts
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](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-threads-threadId/index.html)

Returns a thread. Throws an error if the room or thread isn’t found. This is a
wrapper around the
[Get Thread API](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-threads-threadId/index.html)
and returns the same response.

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

#### [Liveblocks.editThreadMetadata](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-threads-threadId-metadata/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads-threadId-metadata/index.html)
and returns the updated metadata.

```ts
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.

```ts
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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-threads-threadId-comments-commentId-metadata/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads-threadId-comments-commentId-metadata/index.html)
and returns the updated metadata.

```ts
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.

```ts
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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-threads-threadId-mark-as-resolved/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads-threadId-mark-as-resolved/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-threads-threadId-mark-as-unresolved/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads-threadId-mark-as-unresolved/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node\#delete-rooms-roomId-threads-threadId/index.html)

Deletes a thread. Throws an error if the room or thread isn’t found. This is a
wrapper around the
[Delete Thread API](/content/docs/api-reference/rest-api-endpoints#delete-rooms-roomId-threads-threadId/index.html)
and returns no response.

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

#### [Liveblocks.subscribeToThread](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-threads-threadId-subscribe/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads-threadId-subscribe/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node#post-rooms-roomId-users-userId-subscription-settings/index.html). This
value can also be overridden by a room-level call that is run afterwards.

```ts
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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-threads-threadId-unsubscribe/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads-threadId-unsubscribe/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node#post-rooms-roomId-users-userId-subscription-settings/index.html). This
value can also be overridden by a room-level call that is run afterwards.

```ts
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](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-threads-threadId-subscriptions/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-threads-threadId-subscriptions/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-threads-threadId-comments/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads-threadId-comments/index.html)
and returns the new comment.

```ts
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`.

```tsx
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.

```ts
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](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-threads-threadId-comments-commentId/index.html)

Returns a comment. Throws an error if the room, thread, or comment isn’t found.
This is a wrapper around the
[Get Comment API](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-threads-threadId-comments-commentId/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-threads-threadId-comments-commentId/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads-threadId-comments-commentId/index.html)
and returns the updated comment.

```ts
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);
```

```tsx
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](/content/docs/api-reference/liveblocks-node\#delete-rooms-roomId-threads-threadId-comments-commentId/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads-threadId-comments-commentId/index.html)
and returns no response.

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

#### [Liveblocks.addCommentReaction](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-threads-threadId-comments-commentId-add-reaction/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads-threadId-comments-commentId-add-reaction/index.html)
and returns the new reaction.

```ts
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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-threads-threadId-comments-commentId-remove-reaction/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-threads-threadId-comments-commentId-remove-reaction/index.html)
and returns no response.

```ts
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](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-users-userId-subscription-settings/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-users-userId-subscription-settings/index.html).

```ts
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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-users-userId-subscription-settings/index.html)

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](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-users-userId-subscription-settings/index.html).

```ts
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](/content/docs/api-reference/liveblocks-node\#Replacing-individual-thread-subscriptions/index.html)

Subscribing will replace any
[existing thread subscriptions](/content/docs/api-reference/liveblocks-node#post-rooms-roomId-users-userId-subscription-settings/index.html)
in the current room. This value can also be overridden by a room-level call that
is run afterwards.

```ts
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](/content/docs/api-reference/liveblocks-node\#delete-rooms-roomId-users-userId-subscription-settings/index.html)

Deletes a user’s subscription settings for a specific room. This is a wrapper
around the
[Delete Room Subscription Settings API](/content/docs/api-reference/rest-api-endpoints#delete-rooms-roomId-users-userId-subscription-settings/index.html).

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

#### [Liveblocks.getUserRoomSubscriptionSettings](/content/docs/api-reference/liveblocks-node\#get-users-userId-room-subscription-settings/index.html)

Returns a list of a user’s subscription settings for all rooms. This is a
wrapper around the
[Get User Room Subscription Settings API](/content/docs/api-reference/rest-api-endpoints#get-users-userId-room-subscription-settings/index.html).

```ts
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](/content/docs/api-reference/liveblocks-node\#Feeds/index.html)

#### [Liveblocks.getFeeds](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-feeds/index.html)

Returns a list of feeds in a room. This is a wrapper around the
[Get Room Feeds API](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-feeds/index.html)
and returns the same response.

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

#### [Liveblocks.createFeed](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-feed/index.html)

Creates a new feed in a room. This is a wrapper around the
[Create Feed API](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-feed/index.html)
and returns the created feed.

```ts
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](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-feeds-feedId/index.html)

Returns a feed by its ID. This is a wrapper around the
[Get Feed API](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-feeds-feedId/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node\#patch-rooms-roomId-feeds-feedId/index.html)

Updates the metadata of a feed. This is a wrapper around the
[Update Feed API](/content/docs/api-reference/rest-api-endpoints#patch-rooms-roomId-feeds-feedId/index.html).

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

#### [Liveblocks.deleteFeed](/content/docs/api-reference/liveblocks-node\#delete-rooms-roomId-feeds-feedId/index.html)

Deletes a feed. This is a wrapper around the
[Delete Feed API](/content/docs/api-reference/rest-api-endpoints#delete-rooms-roomId-feeds-feedId/index.html).

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

#### [Liveblocks.getFeedMessages](/content/docs/api-reference/liveblocks-node\#get-rooms-roomId-feeds-feedId-messages/index.html)

Returns a list of messages in a feed. This is a wrapper around the
[Get Feed Messages API](/content/docs/api-reference/rest-api-endpoints#get-rooms-roomId-feeds-feedId-messages/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node\#post-rooms-roomId-feeds-feedId-messages/index.html)

Creates a new message in a feed. This is a wrapper around the
[Create Feed Message API](/content/docs/api-reference/rest-api-endpoints#post-rooms-roomId-feeds-feedId-messages/index.html)
and returns the created message.

```ts
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](/content/docs/api-reference/liveblocks-node\#patch-rooms-roomId-feeds-feedId-messages-messageId/index.html)

Updates a feed message. This is a wrapper around the
[Update Feed Message API](/content/docs/api-reference/rest-api-endpoints#patch-rooms-roomId-feeds-feedId-messages-messageId/index.html).

```ts
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](/content/docs/api-reference/liveblocks-node\#delete-rooms-roomId-feeds-feedId-messages-messageId/index.html)

Deletes a feed message. This is a wrapper around the
[Delete Feed Message API](/content/docs/api-reference/rest-api-endpoints#delete-rooms-roomId-feeds-feedId-messages-messageId/index.html).

```ts
await liveblocks.deleteFeedMessage({  roomId: "my-room-id",  feedId: "my-feed-id",  messageId: "my-message-id",});
```

### [Notifications](/content/docs/api-reference/liveblocks-node\#Notifications/index.html)

#### [Liveblocks.getInboxNotifications](/content/docs/api-reference/liveblocks-node\#get-users-userId-inboxNotifications/index.html)

Returns a list of a user’s inbox notifications. This is a wrapper around the
[Get Inbox Notifications API](/content/docs/api-reference/rest-api-endpoints#get-users-userId-inboxNotifications/index.html).
It also provides an unread query parameter to filter unread notifications.

```ts
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](/content/docs/api-reference/liveblocks-node\#Pagination/index.html)

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

```ts
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`](/content/docs/api-reference/liveblocks-node#iter-users-userId-inboxNotifications/index.html)
instead. This method automatically paginates your API requests.

#### [Liveblocks.iterInboxNotifications](/content/docs/api-reference/liveblocks-node\#iter-users-userId-inboxNotifications/index.html)

Returns a list of inbox notifications for the given user. Works similarly to
[`liveblocks.getInboxNotifications`](/content/docs/api-reference/liveblocks-node#get-users-userId-inboxNotifications/index.html), but
instead returns an asynchronous iterator, which helps you iterate over all the
inbox notifications, without having to manually paginate through the results.

```ts
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](/content/docs/api-reference/liveblocks-node\#get-users-userId-inboxNotifications-inboxNotificationId/index.html)

Returns a user’s inbox notification. This is a wrapper around the
[Get Inbox Notification API](/content/docs/api-reference/rest-api-endpoints#get-users-userId-inboxNotifications-inboxNotificationId/index.html).

```ts
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](/content/docs/api-reference/liveblocks-node\#post-inbox-notifications-trigger/index.html)

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](/content/docs/api-reference/liveblocks-node#Batching-custom-notifications/index.html). This is
a wrapper around the
[Trigger Inbox Notification API](/content/docs/api-reference/rest-api-endpoints#post-inbox-notifications-trigger/index.html).

```ts
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](/content/docs/api-reference/liveblocks-node\#Typing-custom-notifications/index.html)

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

liveblocks.config.ts

```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](/content/docs/api-reference/liveblocks-node\#Batching-custom-notifications/index.html)

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](/content/docs/ready-made-features/notifications/concepts#Notification-batching/index.html).
Next, trigger a notification with the same `subjectId` as an existing
notification, and the result will be added to the `activityData` array.

```ts
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](/content/docs/api-reference/liveblocks-node\#delete-users-userId-inboxNotifications-inboxNotificationId/index.html)

Deletes a user’s inbox notification. This is a wrapper around the
[Delete Inbox Notification API](/content/docs/api-reference/rest-api-endpoints#delete-users-userId-inboxNotifications-inboxNotificationId/index.html).

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

#### [Liveblocks.deleteAllInboxNotifications](/content/docs/api-reference/liveblocks-node\#delete-users-userId-inboxNotifications/index.html)

Deletes all the user’s inbox notifications. This is a wrapper around the
[Delete Inbox Notifications API](/content/docs/api-reference/rest-api-endpoints#delete-users-userId-inboxNotifications/index.html).

```ts
await liveblocks.deleteAllInboxNotifications({  userId: "steven@example.com",  // Optional, delete for a specific organization  organizationId: "acme-corp",});
```

#### [Liveblocks.getNotificationSettingsBeta](/content/docs/api-reference/liveblocks-node\#get-users-userId-notification-settings/index.html)

Returns a user’s notification settings in the current project, in other words
which [notification webhook events](/content/docs/platform/webhooks#NotificationEvent/index.html)
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](/content/docs/api-reference/rest-api-endpoints#get-users-userId-notification-settings/index.html).

```ts
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:

```ts
const settings = await liveblocks.getNotificationSettings({  userId: "guillaume@liveblocks.io",});
// { email: null, ... }console.log(settings);
```

#### [Liveblocks.updateNotificationSettingsBeta](/content/docs/api-reference/liveblocks-node\#post-users-userId-notification-settings/index.html)

Updates a user’s notification settings, which affects which
[notification webhook events](/content/docs/platform/webhooks#NotificationEvent/index.html) 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](/content/docs/api-reference/rest-api-endpoints#post-users-userId-notification-settings/index.html).

```ts
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.

```ts
// 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](/content/docs/api-reference/liveblocks-node\#delete-users-userId-notification-settings/index.html)

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](/content/docs/api-reference/rest-api-endpoints#delete-users-userId-notification-settings/index.html).

```ts
await liveblocks.deleteNotificationSettings({  userId: "adri@example.com",});
```

### [AI Copilots](/content/docs/api-reference/liveblocks-node\#AI-Copilots/index.html)

#### [Liveblocks.getAiCopilots](/content/docs/api-reference/liveblocks-node\#get-ai-copilots/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-ai-copilots/index.html)
and returns the same response.

```ts
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.

```ts
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](/content/docs/api-reference/liveblocks-node\#create-ai-copilot/index.html)

Creates a new AI copilot with the given configuration. This is a wrapper around
the
[Create AI Copilot API](/content/docs/api-reference/rest-api-endpoints#create-ai-copilot/index.html)
and returns the same response.

```ts
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.

```ts
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:

```ts
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:

```ts
// 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](/content/docs/api-reference/liveblocks-node\#get-ai-copilot/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-ai-copilot/index.html) and
returns the same response.

```ts
const copilot = await liveblocks.getAiCopilot("co_abc123...");
// { type: "copilot", id: "co_abc123...", name: "My AI Assistant", ... }console.log(copilot);
```

#### [Liveblocks.updateAiCopilot](/content/docs/api-reference/liveblocks-node\#update-ai-copilot/index.html)

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](/content/docs/api-reference/rest-api-endpoints#update-ai-copilot/index.html)
and returns the same response.

```ts
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:

```ts
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):

```ts
// 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`.

```ts
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](/content/docs/api-reference/liveblocks-node\#delete-ai-copilot/index.html)

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](/content/docs/api-reference/rest-api-endpoints#delete-ai-copilot/index.html)
and returns no response.

```ts
await liveblocks.deleteAiCopilot("co_abc123...");
```

### [Knowledge Sources](/content/docs/api-reference/liveblocks-node\#Knowledge-Sources/index.html)

#### [Liveblocks.createWebKnowledgeSource](/content/docs/api-reference/liveblocks-node\#create-web-knowledge-source/index.html)

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](/content/docs/api-reference/rest-api-endpoints#create-web-knowledge-source/index.html)
and returns the ID of the created knowledge source.

```ts
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:

```ts
// 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](/content/docs/api-reference/liveblocks-node\#create-file-knowledge-source/index.html)

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](/content/docs/api-reference/rest-api-endpoints#create-file-knowledge-source/index.html)
and returns the ID of the created knowledge source.

**Note:** Currently only PDF files (`application/pdf`) and images (`image/*`)
are supported.

```ts
const { id } = await liveblocks.createFileKnowledgeSource({  copilotId: "co_abc123...",  file: pdfFile, // Must be a PDF or image file});
// "ks_ghi789..."console.log(id);
```

#### [Liveblocks.getKnowledgeSources](/content/docs/api-reference/liveblocks-node\#get-knowledge-sources/index.html)

Returns a paginated list of knowledge sources for a specific AI copilot. This is
a wrapper around the
[Get Knowledge Sources API](/content/docs/api-reference/rest-api-endpoints#get-knowledge-sources/index.html)
and returns the same response.

```ts
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:

```ts
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](/content/docs/api-reference/liveblocks-node\#get-knowledge-source/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-knowledge-source/index.html)
and returns the same response.

```ts
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](/content/docs/api-reference/liveblocks-node\#get-file-knowledge-source-markdown/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-file-knowledge-source-content/index.html)
and returns the content as a string.

```ts
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](/content/docs/api-reference/liveblocks-node\#get-web-knowledge-source-links/index.html)

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](/content/docs/api-reference/rest-api-endpoints#get-web-knowledge-source-links/index.html)
and returns the same response.

```ts
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:

```ts
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](/content/docs/api-reference/liveblocks-node\#delete-web-knowledge-source/index.html)

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](/content/docs/api-reference/rest-api-endpoints#delete-web-knowledge-source/index.html)
and returns no response.

```ts
await liveblocks.deleteWebKnowledgeSource({  copilotId: "co_abc123...",  knowledgeSourceId: "ks_def456...",});
```

#### [Liveblocks.deleteFileKnowledgeSource](/content/docs/api-reference/liveblocks-node\#delete-file-knowledge-source/index.html)

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](/content/docs/api-reference/rest-api-endpoints#delete-file-knowledge-source/index.html)
and returns no response.

```ts
await liveblocks.deleteFileKnowledgeSource({  copilotId: "co_abc123...",  knowledgeSourceId: "ks_def456...",});
```

### [Error handling](/content/docs/api-reference/liveblocks-node\#error-handling/index.html)

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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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:

```ts
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](/content/docs/api-reference/liveblocks-node\#Utilities/index.html)

### [getMentionsFromCommentBody](/content/docs/api-reference/liveblocks-node\#get-mentions-from-comment-body/index.html)

Returns an array of mentions from a `CommentBody` (found under `comment.body`).

```ts
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).

```tsx
// 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](/content/docs/api-reference/liveblocks-node#Comments/index.html), for
example [`getComment`](/content/docs/api-reference/liveblocks-node#get-comment/index.html).

[Sign in](/content/dashboard/apikeys/index.html) to get your own API keys.

```ts
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`.

```ts
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`](/content/docs/api-reference/liveblocks-client#get-mentions-from-comment-body/index.html).

### [stringifyCommentBody](/content/docs/api-reference/liveblocks-node\#stringify-comment-body/index.html)

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

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

[Sign in](/content/dashboard/apikeys/index.html) to get your own API keys.

```ts
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.

```ts
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`](/content/docs/api-reference/liveblocks-client#stringify-comment-body/index.html).

#### [Formatting examples](/content/docs/api-reference/liveblocks-node\#Formatting-examples/index.html)

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

```ts
// "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](/content/docs/api-reference/liveblocks-node\#WebhookHandler/index.html)

Need help implementing webhooks?

Read the [Webhooks guide](/content/docs/platform/webhooks/index.html) 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.

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

#### [verifyRequest](/content/docs/api-reference/liveblocks-node\#verifyRequest/index.html)

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

```js
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.

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

##### [Example using Next.js](/content/docs/api-reference/liveblocks-node\#webhook-example/index.html)

```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](/content/docs/api-reference/liveblocks-node\#isThreadNotificationEvent/index.html)

Type guard to check if a received webhook event is a
[`ThreadNotificationEvent`](/content/docs/platform/webhooks#Thread-notification/index.html) send
from Comments. Particularly helpful when creating
[thread notification emails](/content/docs/api-reference/liveblocks-emails#thread-notification-emails/index.html)
with webhooks.

```js
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](/content/docs/api-reference/liveblocks-node\#isTextMentionNotificationEvent/index.html)

Type guard to check if a received webhook event is a
[`TextMentionNotificationEvent`](/content/docs/platform/webhooks#TextMention-notification/index.html)
sent from Text Editor. Particularly helpful for identifying text mentions when
sending email notifications.

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

### [isCustomNotificationEvent](/content/docs/api-reference/liveblocks-node\#isCustomNotificationEvent/index.html)

Type guard to check if a received webhook event is a
[`CustomNotificationEvent`](/content/docs/platform/webhooks#Custom-notification/index.html) sent
from
[`triggerInboxNotification`](/content/docs/api-reference/liveblocks-node#post-inbox-notifications-trigger/index.html).
Particularly helpful for identifying custom notifications when sending email
notifications.

```js
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.
