API Reference - @liveblocks/client

@liveblocks/client provides you with JavaScript bindings for our real-time collaboration APIs, built on top of WebSockets. Read our getting started guides to learn more.

createClient

Creates a client that allows you to connect to Liveblocks servers. You must define either authEndpoint or publicApiKey. Resolver functions should be placed inside here, and a number of other options are available.

import { createClient } from "@liveblocks/client";
const client = createClient({ authEndpoint: "/api/liveblocks-auth",
// Other options // ...});
Returns
  • clientClient

    Returns a Client, used for connecting to Liveblocks.

Options
  • authEndpoint

    The URL of your back end’s authentication endpoint as a string, or an async callback function that returns a Liveblocks token result. Either authEndpoint or publicApikey are required. Learn more about using a URL string and using a callback.

  • publicApiKeystring

    The public API key taken from your project’s dashboard. Generally not recommended for production use. Either authEndpoint or publicApikey are required. Learn more.

  • throttlenumberDefault is 100

    The throttle time between WebSocket messages in milliseconds, a number between 16 and 1000 is allowed. Using 16 means your app will update 60 times per second. Learn more.

  • lostConnectionTimeoutnumberDefault is 5000

    After a user disconnects, the time in milliseconds before a "lost-connection" event is fired. Learn more.

  • backgroundKeepAliveTimeoutnumber

    The time before an inactive WebSocket connection is disconnected. This is disabled by default, but setting a number will activate it. Learn more.

  • resolveUsers

    A function that resolves user information in Comments. Return an array of TUserMeta["info"] objects in the same order they arrived. Learn more.

  • resolveRoomsInfo

    A function that resolves room information in Comments. Return an array of RoomInfo objects in the same order they arrived. Learn more.

  • resolveMentionSuggestions

    A function that resolves mention suggestions in Comments. Return an array of user IDs. Learn more.

  • polyfills

    Place polyfills for atob, fetch, and WebSocket inside here. Useful when using a non-browser environment, such as Node.js or React Native.

  • unstable_fallbackToHTTPbooleanDefault is false

    Experimental. Automatically fall back to HTTP when a message is too large for WebSockets.

  • unstable_streamDatabooleanDefault is false

    Experimental. Stream the initial Storage content over HTTP, instead of waiting for a large initial WebSocket message to be sent from the server.

createClient with public key

When creating a client with a public key, you don’t need to set up an authorization endpoint. We only recommend using a public key when prototyping, or on public landing pages, as it makes it possible for end users to access any room’s data. You should instead use an auth endpoint.

import { createClient } from "@liveblocks/client";
const client = createClient({ publicApiKey: "",});

createClient with auth endpoint

If you are not using a public key, you need to set up your own authEndpoint. Please refer to our Authentication guide.

import { createClient } from "@liveblocks/client";
const client = createClient({ authEndpoint: "/api/liveblocks-auth" });

createClient with auth endpoint callback

If you need to add additional headers or use your own function to call your endpoint, authEndpoint can be provided as a custom callback. You should return the token created with Liveblocks.prepareSession or liveblocks.identifyUser, learn more in authentication guide.

import { createClient } from "@liveblocks/client";
const client = createClient({ authEndpoint: async (room) => { // Fetch your authentication endpoint and retrieve your access or ID token // ...
return '{ "token": "..." }'; },});

room is the room ID that the user is connecting to. When using Notifications, room can be undefined, as the client is requesting a token that grants access to multiple rooms, rather than a specific room.

Fetch your endpoint

Here’s an example of fetching your API endpoint at /api/liveblocks-auth within the callback.

import { createClient } from "@liveblocks/client";
const client = createClient({ authEndpoint: async (room) => { const response = await fetch("/api/liveblocks-auth", { method: "POST", headers: { Authentication: "<your own headers here>", "Content-Type": "application/json", }, // Don't forget to pass `room` down. Note that it // can be undefined when using Notifications. body: JSON.stringify({ room }), }); return await response.json(); },});

Token details

You should return the token created with Liveblocks.prepareSession or liveblocks.identifyUser. These are the values the functions can return.

  1. A valid token, it returns a '{ "token": "..." }' shaped response.
  2. A token that explicitly forbids access, it returns an '{ "error": "forbidden", "reason": "..." }' shaped response. If this is returned, the client will disconnect and won't keep trying to authorize.

Any other error will be treated as an unexpected error, after which the client will retry the request until it receives either 1. or 2.

WebSocket throttle

By default, the client throttles the WebSocket messages sent to one every 100 milliseconds, which translates to 10 updates per second. It’s possible to override that configuration with the throttle option with a value between 16 and 1000 milliseconds.

import { createClient } from "@liveblocks/client";
const client = createClient({ throttle: 16,
// Other options // ...});

This option is helpful for smoothing out real-time animations in your application, as you can effectively increase the framerate without using any interpolation. Here are some examples with their approximate frames per second (FPS) values.

throttle:  16, // 60 FPSthrottle:  32, // 30 FPSthrottle: 200, //  5 FPS

Lost connection timeout

If you’re connected to a room and briefly lose connection, Liveblocks will reconnect automatically and quickly. However, if reconnecting takes longer than usual, for example if your network is offline, then the room will emit an event informing you about this.

How quickly this event is triggered can be configured with the lostConnectionTimeout setting, and it takes a number in milliseconds. lostConnectionTimeout can be set between 1000 and 30000 milliseconds. The default is 5000, or 5 seconds.

import { createClient } from "@liveblocks/client";
const client = createClient({ lostConnectionTimeout: 5000,
// Other options // ...});

You can listen to the event with room.subscribe("lost-connection"). Note that this also affects when others are reset to an empty array after a disconnection. This helps prevent temporary flashes in your application as a user quickly disconnects and reconnects. For a demonstration of this behavior, see our connection status example.

Background keep-alive timeout

By default, Liveblocks applications will maintain an active WebSocket connection to the Liveblocks servers, even when running in a browser tab that’s in the background. However, if you’d prefer for background tabs to disconnect after a period of inactivity, then you can use backgroundKeepAliveTimeout.

When backgroundKeepAliveTimeout is specified, the client will automatically disconnect applications that have been in an unfocused background tab for at least the specified time. When the browser tab is refocused, the client will immediately reconnect to the room and synchronize the document.

import { createClient } from "@liveblocks/client";
const client = createClient({ // Disconnect users after 15 minutes of inactivity backgroundKeepAliveTimeout: 15 * 60 * 1000,
// Other options // ...});

backgroundKeepAliveTimeout accepts a number in milliseconds—we advise using a value of at least a few minutes, to avoid unnecessary disconnections.

resolveUsers

Comments stores user IDs in its system, but no other user information. To display user information in Comments components, such as a user’s name or avatar, you need to resolve these IDs into user objects. This function receives a list of user IDs and you should return a list of user objects of the same size, in the same order.

import { createClient } from "@liveblocks/client";
const client = createClient({ resolveUsers: async ({ userIds }) => { const usersData = await (userIds);
return usersData.map((userData) => ({ name: userData.name, avatar: userData.avatar.src, })); },
// Other options // ...});

The name and avatar you return are rendered in Thread components.

User objects

The user objects returned by the resolver function take the shape of UserMeta["info"], which contains name and avatar by default. These two values are optional, though if you’re using the Comments default components, they are necessary. Here’s an example of userIds and the exact values returned.

resolveUsers: async ({ userIds }) => {  // ["marc@example.com", "nimesh@example.com"];  console.log(userIds);
return [ { name: "Marc", avatar: "https://example.com/marc.png" }, { name: "Nimesh", avatar: "https://example.com/nimesh.png" }, ];};

You can also return custom information, for example, a user’s color:

resolveUsers: async ({ userIds }) => {  // ["marc@example.com"];  console.log(userIds);
return [ { name: "Marc", avatar: "https://example.com/marc.png", color: "purple", }, ];};

Accessing user data in React

You can access any values set within resolveUsers with the useUser hook.

import { useUser } from "../liveblocks.config";
function Component() { const user = useUser("marc@example.com");
// { name: "Marc", avatar: "https://...", ... } console.log(user);}

resolveRoomsInfo

When using Notifications with Comments, room IDs will be used to contextualize notifications (e.g. “Chris mentioned you in room-id”) in the InboxNotification component. To replace room IDs with more fitting names (e.g. document names, “Chris mentioned you in Document A”), you can provide a resolver function to the resolveRoomsInfo option in createClient.

This resolver function will receive a list of room IDs and should return a list of room info objects of the same size and in the same order.

import { createClient } from "@liveblocks/client";
const client = createClient({ resolveRoomsInfo: async ({ roomIds }) => { const documentsData = await (roomIds);
return documentsData.map((documentData) => ({ name: documentData.name, // url: documentData.url, })); },
// Other options // ...});

In addition to the room’s name, you can also provide a room’s URL as the url property. If you do so, the InboxNotification component will automatically use it. It’s possible to use an inbox notification’s roomId property to construct a room’s URL directly in React and set it on InboxNotification via href, but the room ID might not be enough for you to construct the URL , you might need to call your backend for example. In that case, providing it via resolveRoomsInfo is the preferred way.

resolveMentionSuggestions

To enable creating mentions in Comments, you can provide a resolver function to the resolveMentionSuggestions option in createClient. These mentions will be displayed in the Composer component.

This resolver function will receive the mention currently being typed (e.g. when writing “@jane”, text will be jane) and should return a list of user IDs matching that text. This function will be called every time the text changes but with some debouncing.

import { createClient } from "@liveblocks/client";
const client = createClient({ resolveMentionSuggestions: async ({ text, roomId }) => { const workspaceUsers = await (roomId);
if (!text) { // Show all workspace users by default return (workspaceUsers); } else { const matchingUsers = (workspaceUsers, text); return (matchingUsers); } },
// Other options // ...});

createClient for Node.js

To use @liveblocks/client in Node.js, you need to provide WebSocket and fetch polyfills. As polyfills, we recommend installing ws and node-fetch.

$npm install ws node-fetch

Then, pass them to the createClient polyfill option as below.

import { createClient } from "@liveblocks/client";import fetch from "node-fetch";import WebSocket from "ws";
const client = createClient({ polyfills: { fetch, WebSocket, },
// Other options // ...});

Note that node-fetch v3+ does not support CommonJS. If you are using CommonJS, downgrade node-fetch to v2.

createClient for React Native

To use @liveblocks/client with React Native, you need to add an atob polyfill. As a polyfill, we recommend installing base-64.

$npm install base-64

Then you can pass the decode function to our atob polyfill option when you create the client.

import { createClient } from "@liveblocks/client";import { decode } from "base-64";
const client = createClient({ polyfills: { atob: decode, },
// Other options // ...});

Client

Client returned by createClient which allows you to connect to Liveblocks servers in your application, and enter rooms.

Client.enterRoom

Enters a room and returns both the local Room instance, and a leave unsubscribe function. The authentication endpoint is called as soon as you call this function. Used for setting initial Presence and initial Storage values.

const { room, leave } = client.enterRoom("my-room-id", {  initialPresence: {},
// Other options // ...});

Note that it’s possible to add types to your room.

Returns
  • roomRoom<TPresence, TStorage, TUserMeta, TRoomEvent>

    A Room, used for building your Liveblocks application. Learn more about typing your room.

  • leave() => void

    A function that’s used to leave the room and disconnect.

Arguments
  • roomIdstringRequired

    The ID of the room you’re connecting to.

  • options.initialPresenceJsonObjectRequired

    The initial Presence of the user entering the room. Each user has their own presence, and this is readable for all other connected users. A user’s Presence resets every time they disconnect. This object must be JSON-serializable. Learn more.

  • options.initialStorageLsonObject

    The initial Storage structure for the room when it’s joined for the first time. This is only set a single time, when the room has not yet been populated. This object must contain conflict-free live structures. Learn more.

  • options.autoConnectbooleanDefault is true

    Whether the room immediately connects to Liveblocks servers.

Setting initial Presence

Presence is used for storing temporary user-based values, such as a user’s cursor coordinates, or their current selection. Each user has their own presence, and this is readable for all other connected users. Set your initial Presence value by using initialPresence.

const { room, leave } = client.enterRoom("my-room-id", {  initialPresence: {    cursor: null,    colors: ["red", "purple"],    selection: {      id: 72426,    },  },
// Other options // ...});

Each user’s Presence resets every time they disconnect, as this is only meant for temporary data. Any JSON-serializable object is allowed (the JsonObject type).

Setting initial Storage

Storage is used to store permanent data that’s used in your application, such as shapes on a whiteboard, nodes on a flowchart, or text in a form. The first time a room is entered, you can set an initial value by using initialStorage. Note that this value is only read a single time.

import { LiveList, LiveObject } from "@liveblocks/client";
const { room, leave } = client.enterRoom("my-room-id", { initialStorage: { title: "Untitled", shapes: new LiveList([ new LiveObject({ type: "rectangle", color: "yellow" }), ]), },
// Other options // ...});

Any conflict-free live structures and JSON-serializable objects are allowed (the LsonObject type).

Client.enterDeprecated

Enters a room and returns its local Room instance.

// ❌ This API was recommended before 1.5const room = client.enter("my-room", {  initialPresence: { cursor: null },  initialStorage: { todos: new LiveList() },});client.leave(roomId);
// ✅ Prefer this insteadconst { room, leave } = client.enterRoom("my-room", { initialPresence: { cursor: null }, initialStorage: { todos: new LiveList() },});leave();

Client.leaveDeprecated

Leaves a room.

// ❌ This API was recommended before 1.5client.leave("my-room");
// ✅ Prefer this insteadconst { room, leave } = client.enterRoom("my-room" /* options */);leave();

Client.getRoom

Gets a room by its ID. Returns null if client.enterRoom has not been called previously.

const room = client.getRoom("my-room");

It’s unlikely you’ll need this API if you’re using the newer client.enterRoom API. Note that it’s possible to add types to your room.

Returns
  • roomRoom<TPresence, TStorage, TUserMeta, TRoomEvent> | null

    A Room, used for building your Liveblocks application. Returns null if the room has not yet been joined by the current client. Learn more about typing your room.

Arguments
  • roomIdstringRequired

    The ID of the room you’re connecting to.

Client.logout

Purges any auth tokens from the client’s memory. If there are any rooms that are still connected, they will be forced to reauthorize.

client.logout();
Returns
Nothing
Arguments
None

When to logout

Use this function if you have a single page application (SPA) and you wish to log your user out, and reauthenticate them. This is a way to update your user’s info after a connection has begun.

Room

Room returned by client.enterRoom (or client.getRoom).

Room.getPresence

Return the current user’s Presence. Presence is used to store custom properties on each user that exist until the user disconnects. An example use would be storing a user’s cursor coordinates.

const presence = room.getPresence();
// { cursor: { x: 363, y: 723 } }console.log(presence);

Presence is set with updatePresence and can be typed when you enter a room. The example above is using the following type:

type Presence = {  cursor: { x: number; y: number };};
Returns
  • presenceTPresence

    An object holding the Presence value for the currently connected user. Presence is set with updatePresence. Will always be JSON-serializable. TPresence is the Presence type you set yourself, learn more.

Arguments
None

Room.updatePresence

Updates the current user’s Presence. Only pass the properties you wish to update—any changes will be merged into the current presence. The entire presence object will not be replaced.

room.updatePresence({ typing: true });room.updatePresence({ status: "Online" });
// { typing: true, status: "Online" }const presence = room.getPresence();
Returns
Nothing
Arguments
  • updateTPresenceRequired

    The updated Presence properties for the current user inside an object. The user’s entire Presence object will not be replaced, instead these properties will be merged with the existing Presence. This object must be JSON-serializable.

  • options.addToHistorybooleanDefault is false

    Adds Presence values to the history stack, meaning using undo and redo functions will change them. Learn more.

Add Presence to history

By default, Presence values are not added to history. However, using the addToHistory option will add items to the undo/redo stack.

room.updatePresence({ color: "blue" }, { addToHistory: true });room.updatePresence({ color: "red" }, { addToHistory: true });room.history.undo();
// { color: "blue" }const presence = room.getPresence();

See room.history for more information.

Room.getOthers

Returns an array of currently connected users in the room. Returns a User object for each user. Note that you can also subscribe to others using Room.subscribe("others").

const others = room.getOthers();
for (const other of others) { const { connectionId, id, info, presence, canWrite, canComment } = other; // Do things}
Returns
  • othersUser<TPresence, TUserMeta>[]

    An array holding each connected user’s User object. User contains the current user’s Presence value, along with other information. Presence is set with updatePresence. Returns an empty array when no other users are currently connected. Will always be JSON-serializable.

Arguments
None

Room.broadcastEvent

Broadcast an event to other users in the Room. Events broadcast to the room can be listened to with Room.subscribe("event"). Takes a custom event payload as first argument. Should be serializable to JSON.

room.broadcastEvent({ type: "REACTION", emoji: "🔥" });
Returns
Nothing
Arguments
  • eventTRoomEventRequired

    The event to broadcast to every other user in the room. Must be JSON-serializable. TRoomEvent is the RoomEvent type you set yourself, learn more.

  • options.shouldQueueEventIfNotReadybooleanDefault is false

    Queue the event if the connection is currently closed, or has not been opened yet. We’re not sure if we want to support this option in the future so it might be deprecated to be replaced by something else. Learn more.

Receiving an event

To receive an event, use Room.subscribe("event"). The user property received on the other end is the sender’s User instance.

// User 1room.broadcastEvent({ type: "REACTION", emoji: "🔥" });
// User 2const unsubscribe = room.subscribe("event", ({ event, user, connectionId }) => { // ^^^^ User 1 if (event.type === "REACTION") { // Do something }});

We recommend using a property such as type, so that it’s easy to distinguish between different events on the receiving end.

Typing multiple events

When defining your types, you can pass a RoomEvent type to the fourth generic in client.enterRoom to receive type hints in your app. To define multiple different custom events, use a union.

type Presence = {};type Storage = {};type UserMeta = {};
type ReactionEvent = { type: "REACTION"; emoji: string };type ActionEvent = { type: "ACTION"; action: string };type RoomEvent = ReactionEvent | ActionEvent;
const { room, leave } = client.enterRoom< Presence, Storage, UserMeta, RoomEvent>("my-room-id", { initialPresence: {} });
room.subscribe("event", ({ event, user, connectionId }) => { if (event.type === "REACTION") { // Do something } if (event.type === "ACTION") { // Do something else }});

Broadcasting an event when disconnected

By default, broadcasting an event is a “fire and forget” action. If the sending client is not currently connected to a room, the event is simply discarded. When passing the shouldQueueEventIfNotReady option, the client will queue up the event, and only send it once the connection to the room is (re)established.

room.broadcastEvent(  { type: "REACTION", emoji: "🔥" },  {    shouldQueueEventIfNotReady: true,  });

Room.getSelf

Gets the current User. Returns null if the client is not yet connected to the room.

const { connectionId, presence, id, info, canWrite, canComment } =  room.getSelf();
Returns
  • userUser<TPresence, TUserMeta> | null

    Returns the current User. Returns null if the client is not yet connected to the room.

Arguments
None

Here’s an example of a full return value, assuming Presence and UserMeta have been set.

const user = room.getSelf();
// {// connectionId: 52,// presence: {// cursor: { x: 263, y: 786 },// },// id: "mislav.abha@example.com",// info: {// avatar: "/mislav.png",// },// canWrite: true,// canComment: true,// }console.log(user);

Room.getStatus

Gets the current WebSocket connection status of the room. The possible value are: initial, connecting, connected, reconnecting, or disconnected.

const status = room.getStatus();
// "connected"console.log(status);
Returns
  • status"initial" | "connecting" | "connected" | "reconnecting" | "disconnected"

    Returns the room’s current connection status. It can return one of five values:

    • "initial" The room has not attempted to connect yet.
    • "connecting" The room is currently authenticating or connecting.
    • "connected" The room is connected.
    • "reconnecting" The room has disconnected, and is trying to connect again.
    • "disconnected" The room is disconnected, and is no longer attempting to connect.
Arguments
None

Room.getStorageStatus

Get the Storage status. Use this to tell whether Storage has been synchronized with the Liveblocks servers.

const status = room.getStorageStatus();
// "synchronizing"console.log(status);
Returns
  • status"not-loaded" | "loading" | "synchronizing" | "synchronized"

    The current room’s Storage status. status can be one of four types.

    • "not-loaded Storage has not been loaded yet as room.getStorage has not been called.
    • "loading" Storage is currently loading for the first time.
    • "synchronizing" Local Storage changes are currently being synchronized.
    • "synchronized" Local Storage changes have been synchronized.
Arguments
None

Room.subscribe(storageItem)

Subscribe to updates on a particular storage item, and takes a callback function that’s called when the storage item is updated. The Storage root is a LiveObject, which means you can subscribe to this, as well as other live structures. Returns an unsubscribe function.

const { root } = await room.getStorage();
const unsubscribe = room.subscribe(root, (updatedRoot) => { // Do something});
Returns
  • unsubscribe() => void

    Unsubscribe function. Call it to cancel the subscription.

Arguments
  • storageItemL extends (LiveObject | LiveMap | LiveList)Required

    The LiveObject, LiveMap, or LiveList which is being subscribed to. Each time the structure is updated, the callback is called.

  • callback(node: L) => voidRequired

    Function that’s called when storageItem updates. Returns the updated storage structure.

  • options.isDeepboolean

    Subscribe to both storageItem and its children. The callback function will be passed a list of updates instead of just the new Storage item. Learn more.

Typing Storage

To type the Storage values you receive, make sure to set your Storage type.

import { LiveList } from "@liveblocks/client";
type Storage = { animals: LiveList<{ name: string }>;};

The type received in the callback will match the type passed. Learn more under typing your room.

const { root } = await room.getStorage();const animals = root.get("animals");
const unsubscribe = room.subscribe(animals, (updatedAnimals) => { // LiveList<[{ name: "Fido" }, { name: "Felix" }]> console.log(updatedAnimals);});

Subscribe to any live structure

You can subscribe to any live structure, be it the Storage root, a child, or a structure even more deeply nested.

import { LiveObject, LiveMap } from "@liveblocks/client";
type Person = LiveObject<{ name: string; age: number }>;
type Storage = { people: LiveMap<string, Person>;};
const { root } = await room.getStorage();const people = root.get("people");const steven = people.get("steven");
const unsubscribeRoot = room.subscribe(root, (updatedRoot) => { // ...});
const unsubscribePeople = room.subscribe(people, (updatedPeople) => { // ...});
const unsubscribeSteven = room.subscribe(steven, (updatedSteven) => { // ...});

Listening for nested changes

It’s also possible to subscribe to a Storage item and all of its children by passing an optional isDeep option in the third argument. In this case, the callback will be passed a list of updates instead of just the new Storage item. Each such update is a { type, node, updates } object.

const { root } = await room.getStorage();
const unsubscribe = room.subscribe( root, (storageUpdates) => { for (const update of storageUpdates) { const { type, // "LiveObject", "LiveList", or "LiveMap" node, updates, } = update; switch (type) { case "LiveObject": { // updates["property"]?.type; is "update" or "delete" // update.node is the LiveObject that has been updated/deleted break; } case "LiveMap": { // updates["key"]?.type; is "update" or "delete" // update.node is the LiveMap that has been updated/deleted break; } case "LiveList": { // updates[0]?.type; is "delete", "insert", "move", or "set" // update.node is the LiveList that has been updated, deleted, or modified break; } } } }, { isDeep: true });

Using async functions

You use an async function inside the subscription callback, though bear in mind that the callback itself is synchronous, and there’s no guarantee the async function will complete before the callback is run again.

const { root } = await room.getStorage();
const unsubscribe = room.subscribe(root, (updatedRoot) => { async function doThing() { await fetch(/* ... */); }
doThing();});

If the order of updates is imporant in your application, and it’s important to ensure that your async function doesn’t start before the previous one finishes, you can use a package such as async-mutex to help you with this. Using runExclusive will effectively form a queue for all upcoming updates, guaranteeing serial execution.

import { Mutex } from "async-mutex";
const { root } = await room.getStorage();const myMutex = new Mutex();
const unsubscribeUpdates = room.subscribe(root, (root) => { void myMutex.runExclusive(async () => { await fetch(/* ... */); });});

Note that this may cause a performance penalty in your application, as certain updates will be ignored.

Room.subscribe("event")

Subscribe to events broadcast by Room.broadcastEvent. Takes a callback that’s run when another user calls Room.broadcastEvent. Provides the event along with the user and their connectionId of the user that sent the message. Returns an unsubscribe function.

// User 1room.broadcastEvent({ type: "REACTION", emoji: "🔥" });
// User 2const unsubscribe = room.subscribe("event", ({ event, user, connectionId }) => { // ^^^^ Will be User 1 if (event.type === "REACTION") { // Do something }});
Returns
  • unsubscribe() => void

    Unsubscribe function. Call it to cancel the subscription.

Arguments
  • eventType"event"Required

    Listen to events.

  • callbackRequired

    Function that’s called when another user sends an event. Receives the event, the user that sent the event, and their connectionId. If this event was sent via liveblocks.broadcastEvent or the Broadcast event API, user will be null and connectionId will be -1. Learn more

Typing events

When defining your types, you can pass a RoomEvent type to the fourth generic in client.enterRoom to receive type hints in your app. To define multiple different custom events, use a union.

type Presence = {};type Storage = {};type UserMeta = {};
type ReactionEvent = { type: "REACTION"; emoji: string };type ActionEvent = { type: "ACTION"; action: string };type RoomEvent = ReactionEvent | ActionEvent;
const { room, leave } = client.enterRoom< Presence, Storage, UserMeta, RoomEvent>("my-room-id", { initialPresence: {} });
room.subscribe("event", ({ event, user, connectionId }) => { if (event.type === "REACTION") { // Do something } if (event.type === "ACTION") { // Do something else }});

Receiving events from the server

Events can be received from the server with either liveblocks.broadcastEvent or the Broadcast Event API. In events sent from the server, user will be null, and connectionId will be -1.

import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: "",});
export async function POST() { await liveblocks.broadcastEvent({ type: "REACTION", emoji: "🔥" });}
const unsubscribe = room.subscribe("event", ({ event, user, connectionId }) => {  // `null`, `-1`  console.log(user, connectionId);});

Room.subscribe("my-presence")

Subscribe to the current user’s Presence. Takes a callback that is called every time the current user presence is updated with Room.updatePresence. Returns an unsubscribe function.

const unsubscribe = room.subscribe("my-presence", (presence) => {  // Do something});
Returns
  • unsubscribe() => void

    Unsubscribe function. Call it to cancel the subscription.

Arguments
  • eventType"my-presence"Required

    Listen to the current user’s presence.

  • callback(presence: TPresence) => voidRequired

    Function that’s called when the current user’s Presence has updated, for example with Room.updatePresence. Receives the updates Presence value.

Typing Presence

To type the Presence values you receive, make sure to set your Presence type.

type Presence = {  status: string;  cursor: { x: number; y: number };};

The type received in the callback will match the type passed. Learn more under typing your room.

const unsubscribe = room.subscribe("my-presence", (presence) => {  // { status: "typing", cursor: { x: 45, y: 67 }  console.log(presence);});

Room.subscribe("others")

Subscribe to every other users’ updates. Takes a callback that’s called when a user’s Presence updates, or when they enter or leave the room. Returns an unsubscribe function.

const unsubscribe = room.subscribe("others", (others, event) => {  // Do something});
Returns
  • unsubscribe() => void

    Unsubscribe function. Call it to cancel the subscription.

Arguments
  • eventType"others"Required

    Listen to others.

  • callback(others: User<TPresence, TUserMeta>[], event: OthersEvent) => voidRequired

    Function that’s called when another user’s Presence has updated, for example with Room.updatePresence, or an others event has occurred. Receives an array of User values for each currently connected user. Also received an object with information about the event that has triggered the update, learn more.

Typing Presence

To type the Presence values you receive, make sure to set your Presence type.

type Presence = {  status: string;  cursor: { x: number; y: number };};

The type received in the callback will match the type passed. Learn more under typing your room.

const unsubscribe = room.subscribe("others", (others, event) => {  // { status: "typing", cursor: { x: 45, y: 67 }  console.log(others[0].presence);});

Listening for others events

The event parameter returns information on why the callback has just run, for example if their Presence has updated, if they’ve just left or entered the room, or if the current user has disconnected.

const unsubscribe = room.subscribe("others", (others, event) => {  if (event.type === "leave") {    // A user has left the room    // event.user;  }
if (event.type === "enter") { // A user has entered the room // event.user; }
if (event.type === "update") { // A user has updated // event.user; // event.updates; }
if (event.type === "reset") { // A disconnection has occurred and others has reset }});

Live cursors

Here’s a basic example showing you how to render live cursors. Room.updatePresence is being used to update each user’s cursor position.

type Presence = {  cursor: { x: number; y: number };};type Storage = {};type UserMeta = { id: string };type RoomEvent = {};
const { room, leave } = client.enterRoom< Presence, Storage, UserMeta, RoomEvent>("my-room-id", { initialPresence: {} });
// Call this to update the current user's Presencefunction updateCursorPosition({ x, y }) { room.updatePresence({ cursor: { x, y } });}
const others = room.getOthers();
// Run __renderCursor__ when any other connected user updates their presenceconst unsubscribe = room.subscribe("others", (others, event) => { for (const { id, presence } of others) { const { x, y } = presence.cursor; (id, { x, y }); }}
// Handle events and rendering// ...

Check our examples page for live demos.

Room.subscribe("status")

Subscribe to WebSocket connection status updates. Takes a callback that is called whenever the connection status changes. Possible value are: initial, connecting, connected, reconnecting, or disconnected. Returns an unsubscribe function.

const unsubscribe = room.subscribe("status", (status) => {  // "connected"  console.log(status);});
Returns
  • unsubscribe() => void

    Unsubscribe function. Call it to cancel the subscription.

Arguments
  • eventType"status"Required

    Listen to status updates.

  • callbackRequired

    Function that’s called when the room’s connection status has changed. It can return one of five values:

    • "initial" The room has not attempted to connect yet.
    • "connecting" The room is currently authenticating or connecting.
    • "connected" The room is connected.
    • "reconnecting" The room has disconnected, and is trying to connect again.
    • "disconnected" The room is disconnected, and is no longer attempting to connect.

When to use status

Status is a low-level API that exposes the WebSocket’s connectivity status. You can use this, for example, to update a connection status indicator in your UI. It would be normal for a client to briefly lose the connection and restore it with quick connectedreconnectingconnected status jumps.

let indicator = "⚪";
const unsubscribe = room.subscribe("status", (status) => { switch (status) { case "connecting": indiciator = "🟡"; break; case "connected": indicator = "🟢"; break; // ... }});

If you’d like to let users know that there may be connectivity issues, don’t use this API, but instead refer to Room.subscribe("lost-connection") which was specially built for this purpose.

Do not use this API to detect when Storage or Presence are initialized or loaded. "Connected" does not guarantee that Storage or Presence are ready. To detect when Storage is loaded, rely on awaiting the Room.getStorage promise or using the Room.subscribe("storage-status") event.

Room.subscribe("lost-connection")

A special-purpose event that will fire when a previously connected Liveblocks client has lost connection, for example due to a network outage, and was unable to recover quickly. This event is designed to help improve UX for your users, and will not trigger on short interruptions, those that are less than 5 seconds by default. The event only triggers if a previously connected client disconnects.

const unsubscribe = room.subscribe("lost-connection", (event) => {  // "lost"  console.log(event);});
Returns
  • unsubscribe() => void

    Unsubscribe function. Call it to cancel the subscription.

Arguments
  • eventType"lost-connection"Required

    Listen to lost connection events.

  • callbackRequired

    Function that’s called when a room’s lost connection event has been triggered. It can return one of three values:

    • "lost" A connection has been lost for longer than lostConnectionTimeout.
    • "restored" The connection has been restored again.
    • "failed" The room has been unable to reconnect again, and is no longer trying. This may happen if a user’s network has recovered, but the room’s authentication values no longer allow them to enter.

When to use lost connection events

Lost connections events allows you to build high-quality UIs by warning your users that the application is still trying to re-establish the connection, for example through a toast notification. You may want to take extra care in the mean time to ensure their changes won’t go unsaved, or to help them understand why they’re not seeing updates made by others yet.

When this happens, this callback is called with the event lost. Then, once the connection restores, the callback will be called with the value restored. If the connection could definitively not be restored, it will be called with failed (uncommon).

import { toast } from "my-preferred-toast-library";
const unsubscribe = room.subscribe("lost-connection", (event) => { switch (event) { case "lost": toast.warn("Still trying to reconnect..."); break;
case "restored": toast.success("Successfully reconnected again!"); break;
case "failed": toast.error("Could not restore the connection"); break; }});

Setting lost connection timeout

The lostConnectionTimeout configuration option will determine how quickly the event triggers after a connection loss occurs. By default, it’s set to 5000ms, which is 5 seconds.

import { createClient } from "@liveblocks/client";
const client = createClient({ // Throw lost-connection event after 5 seconds offline lostConnectionTimeout: 5000,
// ...});

Room.subscribe("connection")

Subscribe to our legacy WebSocket connection status updates. Takes a callback that is called when the connection status changes. Returns an unsubscribe function.

const unsubscribe = room.subscribe("connection", (status) => {  // Do something});

Room.subscribe("error")

Subscribe to unrecoverable room connection errors. This event will be emitted immediately before the client disconnects and won’t try reconnecting again. Returns an unsubscribe function. If you’d like to retry connecting, call room.reconnect.

const unsubscribe = room.subscribe("error", (error) => {  switch (error.code) {    case -1:      // Authentication error      break;
case 4001: // Could not connect because you don't have access to this room break;
case 4005: // Could not connect because room was full break;
case 4006: // The room ID has changed, get the new room ID (use this for redirecting) const newRoomId = error.message; break;
default: // Unexpected error break; }});
Returns
  • unsubscribe() => void

    Unsubscribe function. Call it to cancel the subscription.

Arguments
  • eventType"error"Required

    Listen to error events.

  • callbackRequired

    Function that’s called when an unrecoverable error event has been triggered. error.code can return one of these values:

    • -1 Authentication error.
    • 4001 Could not connect because you don't have access to this room.
    • 4005 Could not connect because room was full.
    • 4006 The room ID has changed.

When to use error events

You can use this event to trigger a “Not allowed” screen/dialog. It can also be helpful for implementing a redirect to another page.

const unsubscribe = room.subscribe("error", (error) => {  // Could not connect because you don't have access to this room  if (error.code === 4001)    return ();  }});

When a room ID has changed

When a room ID has been changed with liveblocks.updateRoomId or the Update Room ID API, error.message will contain the new room ID.

const unsubscribe = room.subscribe("error", (error) => {  // The room ID has changed, get the new room ID  if (error.code === 4006)    const newRoomId = error.message;    return (`/app/${newRoomId}`)  }});

Room.subscribe("history")

Subscribe to the current user’s history changes. Returns an unsubscribe function.

const unsubscribe = room.subscribe("history", ({ canUndo, canRedo }) => {  // Do something});
Returns
  • unsubscribe() => void

    Unsubscribe function. Call it to cancel the subscription.

Arguments
  • eventType"history"Required

    Listen to history events.

  • callbackRequired

    Function that’s called when the current user’s history changes. Returns booleans that describe whether the user can use undo or redo.

Room.subscribe("storage-status")

Subscribe to Storage status changes. Use this to tell whether Storage has been synchronized with the Liveblocks servers. Returns an unsubscribe function.

const unsubscribe = room.subscribe("storage-status", (status) => {  switch (status) {    case "not-loaded":      // Storage has not been loaded yet      break;    case "loading":      // Storage is currently loading      break;    case "synchronizing":      // Local Storage changes are being synchronized      break;    case "synchronized":      // Local Storage changes have been synchronized      break;  }});
Returns
  • unsubscribe() => void

    Unsubscribe function. Call it to cancel the subscription.

Arguments
  • eventType"storage-status"Required

    Listen to Storage status events.

  • callbackRequired

    Function that’s called when the current user’s Storage updated status have changed. status can be one of four types.

    • "not-loaded - Storage has not been loaded yet as [getStorage][] has not been called.
    • "loading" - Storage is currently loading for the first time.
    • "synchronizing" - Local Storage changes are currently being synchronized.
    • "synchronized" - Local Storage changes have been synchronized

Room.batch

Batches Storage and Presence modifications made during the given function. Each modification is grouped together, which means that other clients receive the changes as a single message after the batch function has run. When undoing or redoing these changes, the entire batch will be undone/redone together instead of atomically.

const { root } = await room.getStorage();
room.batch(() => { root.set("x", 0); room.updatePresence({ cursor: { x: 100, y: 100 } });});
Returns
  • returnT

    Returns the return value from the callback.

Arguments
  • callback() => TRequired

    A callback containing every Storage and Presence notification that will be part of the batch. Cannot be an async function.

When to batch updates

For the most part, you don’t need to batch updates. For example, given a whiteboard application, it’s perfectly fine to update a note’s position on the board multiple times per second, in separate updates. However, should you implement a “Delete all” button, that may delete 50 notes at once, this is where you should use a batch.

const { root } = await room.getStorage();const notes = root.get("notes");
// ✅ Batch simultaneous changes togetherroom.batch(() => { for (const noteId of notes.keys()) { notes.delete(noteId); }});

This batch places each LiveMap.delete call into a single WebSocket update, instead of sending multiple updates. This will be much quicker.

Batching groups history changes

Batching changes will also group changes into a single history state.

const { root } = await room.getStorage();const pet = root.set("pet", new LiveObject({ name: "Fido", age: 5 }));
// ✅ Batch groups changes into oneroom.batch(() => { pet.set("name", "Felix"); pet.set("age", 10);});
// { name: "Felix", age: 10 }pet.toImmutable();
room.history.undo();
// { name: "Fido", age: 5 }pet.toImmutable();

Doesn’t work with async functions

Note that room.batch cannot take an async function.

// ❌ Won't workroom.batch(async () => {  // ...});
// ✅ Will workroom.batch(() => { // ...});

Room.history

Room’s history contains functions that let you undo and redo operations made to Storage and Presence on the current client. Each user has a separate history stored in memory, and history is reset when the page is reloaded.

const { undo, redo, pause, resume /*, ... */ } = room.history;

Add Presence to history

By default, history is only enabled for Storage. However, you can use the addToHistory option to additionally add Presence state to history.

room.updatePresence({ color: "blue" }, { addToHistory: true });

Room.history.undo

Reverts the last operation. It does not impact operations made by other clients, and will only undo changes made by the current client.

const person = new LiveObject();person.set("name", "Pierre");person.set("name", "Jonathan");
room.history.undo();
// "Pierre"root.get("name");
Returns
Nothing
Arguments
None

Room.history.redo

Restores the last undone operation. It does not impact operations made by other clients, and will only restore changes made by the current client.

const person = new LiveObject();person.set("name", "Pierre");person.set("name", "Jonathan");
room.history.undo();room.history.redo();
// "Jonathan"root.get("name");
Returns
Nothing
Arguments
None

Room.history.canUndo

Returns true or false, depending on whether there are any operations to undo. Helpful for disabling undo buttons.

const person = new LiveObject();person.set("name", "Pierre");
// trueroom.history.canUndo();
room.history.undo();
// falseroom.history.canUndo();
Returns
  • canUndoboolean

    Whether there is an undo operation in the current history stack.

Arguments
None

Room.history.canRedo

Returns true or false, depending on whether there are any operations to redo. Helpful for disabling redo buttons.

const person = new LiveObject();person.set("name", "Pierre");
// falseroom.history.canRedo();
room.history.undo();
// trueroom.history.canRedo();
Returns
  • canRedoboolean

    Whether there is a redo operation in the current history stack.

Arguments
None

Room.history.clear

Clears the undo and redo stacks for the current client. Explicitly clearing history resets the ability to undo beyond the current document state. Other clients’ histories are unaffected.

const person = new LiveObject();person.set("name", "Pierre");
// trueroom.history.canUndo();
room.history.clear();
// falseroom.history.canUndo();
Returns
Nothing
Arguments
None

Room.history.pause

All future modifications made on the Room will be merged together to create a single history item until resume is called.

const info = new LiveObject({ time: "one" });
room.history.pause();info.set("time", "two");info.set("time", "three");room.history.resume();
room.history.undo();
// "one"room.get("time");
Returns
Nothing
Arguments
None

Room.history.resume

Resumes history after a pause. Modifications made on the Room are not merged into a single history item any more.

const info = new LiveObject({ time: "one" });
room.history.pause();info.set("time", "two");info.set("time", "three");room.history.resume();
room.history.undo();
// "one"room.get("time");
Returns
Nothing
Arguments
None

Room.connect

Connect the local room instance to the Liveblocks server. Does nothing if the room is already connecting, reconnecting or connected. We don’t recommend using this API directly.

room.connect();
Returns
Nothing
Arguments
None

Room.reconnect

Reconnect the local room instance to the Liveblocks server, using a new WebSocket connection.

room.reconnect();
Returns
Nothing
Arguments
None

Room.disconnect

Disconnect the local room instance from the Liveblocks server. The room instance will remain functional (for example, it will still allow local presence or storage mutations), but since it’s no longer connected, changes will not be persisted or synchronized until the room instance is reconnected again. We don’t recommend using this API directly.

room.disconnect();
Returns
Nothing
Arguments
None

Storage

Each room contains Storage, a conflict-free data store that multiple users can edit at the same time. When users make edits simultaneously, conflicts are resolved automatically, and each user will see the same state. Storage is ideal for storing permanent document state, such as shapes on a canvas, notes on a whiteboard, or cells in a spreadsheet.

Data structures

Storage provides three different conflict-free data structures, which you can use to build your application. All structures are permanent and persist when all users have left the room, unlike Presence which is temporary.

  • LiveObject - Similar to JavaScript object. Use this for storing records with fixed key names and where the values don’t necessarily have the same types. For example, a Person with a name: string and an age: number field. If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.

  • LiveList - An ordered collection of items synchronized across clients. Even if multiple users add/remove/move elements simultaneously, LiveList will solve the conflicts to ensure everyone sees the same collection of items.

  • LiveMap - Similar to a JavaScript Map. Use this for indexing values that all have the same structure. For example, to store an index of Person values by their name. If multiple users update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.

Typing Storage

To type the Storage values you receive, make sure to set your Storage type.

import { LiveList } from "@liveblocks/client";
type Storage = { animals: LiveList<{ name: string }>;};

The type received in the callback will match the type passed. Learn more under typing your room.

const { root } = await room.getStorage();const animals = root.get("animals");
const unsubscribe = room.subscribe(animals, (updatedAnimals) => { // LiveList<[{ name: "Fido" }, { name: "Felix" }]> console.log(updatedAnimals);});

Nesting data structures

All Storage data structures can be nested, allowing you to create complex trees of conflict-free data.

import { LiveObject, LiveList, LiveMap } from "@liveblocks/client";
type Person = LiveObject<{ name: string; pets: LiveList<string>;}>;
type Storage = { people: LiveMap<string, Person>;};
import { LiveObject, LiveList, LiveMap } from "@liveblocks/client";
const pets = new LiveList(["Cat", "Dog"]);const person = new LiveObject({ name: "Alicia", pets });const people = new LiveMap();people.set("alicia", person);
const { root } = await room.getStorage();root.set(people);

Room.getStorage

Get the room’s Storage asynchronously (returns a Promise). The promise will resolve once the Storage’s root is loaded and available. The Storage’s root is always a LiveObject.

const { root } = await room.getStorage();
Returns
  • storage{ root: LiveObject<TStorage> }

    The room’s Storage structures. root is a LiveObject, and is the root of your Storage. Learn more about typing Storage.

Arguments
None

LiveObject

The LiveObject class is similar to a JavaScript object that is synchronized on all clients. Use this for storing records with fixed key names and where the values don’t necessarily have the same types. For example, a Person with name and age fields. To add typing, read more under typing Storage.

type Person = LiveObject<{  name: string;  age: number;}>;

Keys are strings, and values can contain other Storage structures, or JSON-serializable data. If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.

new LiveObject

Create an empty LiveObject

import { LiveObject } from "@liveblocks/client";
const object = new LiveObject();

Create a LiveObject with initial data.

import { LiveObject } from "@liveblocks/client";
const object = new LiveObject({ firstName: "Margaret", lastName: "Hamilton" });
Returns
  • LiveObjectLiveObject<L>

    The newly created LiveObject.

Arguments
  • initialValueL extends LsonObjectRequired

    The initial value for the LiveObject. Can contain JSON-serializable data and other Liveblocks conflict-free data structures.

Add a LiveObject to Storage

The Storage root is LiveObject itself, so you can use LiveObject.set to add a new property to your root. If you’ve typed Storage you’ll have type hints as you build.

import { LiveObject } from "@liveblocks/client";
const { root } = await room.getStorage();
const person = new LiveObject({ name: "Alicia" });root.set("person", person);

delete

Delete a property from the LiveObject

const object = new LiveObject({ firstName: "Ada", lastName: "Lovelace" });object.delete("lastName");
// { firstName: "Ada" }object.toImmutable();
Returns
Nothing
Arguments
  • keystringRequired

    The key of the property you’re deleting. If the property doesn’t exist, nothing will occur.

get

Get a property from the LiveObject.

const object = new LiveObject({ firstName: "Ada", lastName: "Lovelace" });
// "Ada"object.get("firstName");
Returns
  • value

    The value of the property. Returns undefined if it doesn’t exist.

Arguments
  • keystringRequired

    The key of the property you’re getting.

set

Adds or updates a property with the specified key and a value.

const object = new LiveObject({ firstName: "Marie" });object.set("lastName", "Curie");
// { firstName: "Ada", lastName: "Curie" }object.toImmutable();
Returns
Nothing
Arguments
  • keystringRequired

    The key of the property you’re setting.

  • valueLsonObjectRequired

    The value of the property you’re setting. Can contain JSON-serializable data and other Liveblocks conflict-free data structures.

update

Adds or updates multiple properties at once. Nested changes to other Storage types will not be applied.

const object = new LiveObject({ firstName: "Grace" });object.update({ lastName: "Hopper", job: "Computer Scientist" });
// { firstName: "Grace", lastName: "Hopper", job: "Computer Scientist" }object.toImmutable();
Returns
Nothing
Arguments
  • valueLsonObjectRequired

    The keys and values you’re updating. Can contain JSON-serializable data and other Liveblocks conflict-free data structures. Nested changes to other Storage types will not be applied.

clone

Returns a deep copy of the LiveObject that can be inserted elsewhere in the Storage tree.

const obj = new LiveObject(/* ... */);root.set("a", obj);root.set("b", obj.clone());
Returns
  • clonedStructureLiveObject

    The cloned LiveObject.

Arguments
None

toImmutable

Returns an immutable JavaScript object that is equivalent to the LiveObject. Nested values will also be immutable.

const liveObject = new LiveObject({  firstName: "Grace",  lastName: "Hopper",  hobbies: new LiveList(["reading", "piano"]),});
// {// firstName: "Grace",// lastName: "Hopper",// hobbies: ["reading", "piano"]// }liveObject.toImmutable();
Returns
  • immutableStructureobject

    Returns a JavaScript object in the shape of your data structure. LiveObject is converted to an object, LiveMap to a map, and LiveList to an array.

Arguments
None

toObject

Transform the LiveObject into a normal JavaScript object.

const liveObject = new LiveObject({ firstName: "Grace", lastName: "Hopper" });liveObject.toObject();// { firstName: "Grace", lastName: "Hopper" }

Please note that this method won’t recursively convert Live structures, which may be surprising:

const liveObject = new LiveObject({  animals: new LiveList(["🦁", "🦊", "🐵"]),});liveObject.toObject();// { animals: <LiveList instance> } // ❗️

LiveMap

The LiveMap class is similar to a JavaScript Map that is synchronized on all clients. Use this for indexing values that all have the same structure. For example, to store an index of Person values by their name. To add typing, read more under typing Storage.

type Shapes = LiveMap<string, LiveObject<{ name: string }>>;

Keys are strings, and values can contain other Storage structures, or JSON-serializable data. If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.

new LiveMap

Create an empty LiveMap.

const map = new LiveMap();

Create a LiveMap with initial data.

const map = new LiveMap([  ["nimesh", "developer"],  ["pierre", "designer"],]);
Returns
  • LiveMapLiveMap<string, L>

    The newly created LiveMap.

Arguments
  • initialValue[string, L extends LsonObject][]Required

    The initial value for the LiveMap. An array of tuples, each containing a key and a value. The values can contain JSON-serializable data and other Liveblocks conflict-free data structures.

Add a LiveMap to Storage

The Storage root is a LiveObject, so you can create a new LiveMap then use LiveObject.set to add it to your root. If you’ve typed Storage you’ll have type hints as you build.

import { LiveMap } from "@liveblocks/client";
const { root } = await room.getStorage();
const people = new LiveMap([ ["vincent", "engineer"], ["marc", "designer"],]);root.set("people", people);

delete

Removes the specified element by key. Returns true if an element existed and has been removed, or false if the element does not exist.

const map = new LiveMap([  ["nimesh", "developer"],  ["pierre", "designer"],]);
// truemap.delete("nimesh");
// Map { "pierre" => "designer" }map.toImmutable();
Returns
  • deletedboolean

    If the element existed and was removed.

Arguments
  • keystringRequired

    The key of the element you’re deleting. If the element doesn’t exist, nothing will occur.

entries

Returns a new Iterator object that contains the [key, value] pairs for each element.

for (const [key, value] of map.entries()) {  // Iterate over all the keys and values of the map}
Returns
  • iteratorIterableIterator<[string, L]>

    A new Iterator object for the LiveMap, containing the [key, value] pairs for each element.

Arguments
None

forEach

Executes a provided function once per each key/value pair in the Map object, in insertion order.

const map = new LiveMap([  ["nimesh", "developer"],  ["pierre", "designer"],]);
// "developer", "designer"map.forEach((value, key, liveMap) => console.log(value));
Returns
Nothing
Arguments
  • callbackRequired

    A callback for each entry. The callback is passed the current value, key, and the LiveMap. Return values are ignored.

get

Returns a specified element from the LiveMap. Returns undefined if the key can’t be found.

const map = new LiveMap([  ["nimesh", "developer"],  ["pierre", "designer"],]);
// "developer"map.get("nimesh");
// undefinedmap.get("alicia");
Returns
  • valueL | undefined

    The value of the entry. Returns undefined if it doesn’t exist.

Arguments
  • keystringRequired

    The key of the entry you’re getting.

has

Returns a boolean indicating whether an element with the specified key exists or not.

const map = new LiveMap([  ["nimesh", "developer"],  ["pierre", "designer"],]);
// truemap.has("nimesh");
// falsemap.has("alicia");
Returns
  • existsboolean

    Whether the entry exists.

Arguments
  • keystringRequired

    The key of the entry you’re getting.

keys

Returns a new Iterator object that contains the keys for each element.

for (const key of map.keys()) {  // Iterate over all the keys and values of the map}
Returns
  • iteratorIterableIterator<string>

    A new Iterator object for the LiveMap, containing the keys of each entry.

Arguments
None

set

Adds or updates an element with a specified key and a value.

const map = new LiveMap();map.set("vincent", "engineer");
// Map { "vincent" => "engineer" }map.toImmutable();
Returns
Nothing
Arguments
  • keystringRequired

    The key of the entry you’re setting.

  • valueLsonObjectRequired

    The value of the entry you’re setting. Can contain JSON-serializable data and other Liveblocks conflict-free data structures.

size

Returns the number of elements in the LiveMap.

const map = new LiveMap([  ["nimesh", "developer"],  ["pierre", "designer"],]);
// 2map.size;
Returns
  • sizenumber

    The number of entries in the LiveMap

Arguments
  • N/A

values

Returns a new Iterator object that contains the the values for each element.

for (const value of map.values()) {  // Iterate over all the values of the map}
Returns
  • iteratorIterableIterator<L>

    A new Iterator object for the LiveMap, containing the values of each entry.

Arguments
None

clone

Returns a deep copy of the LiveMap that can be inserted elsewhere in the Storage tree.

const map = new LiveMap(/* ... */);root.set("a", map);root.set("b", map.clone());
Returns
  • clonedStructureLiveMap

    The cloned LiveMap.

Arguments
None

toImmutable

Returns an immutable ES6 Map that is equivalent to the LiveMap. Nested values will also be immutable.

const map = new LiveMap([  ["florent", new LiveObject({ role: "engineer" })],  ["marc", new LiveObject({ role: "designer" })],]);
// Map {// "florent" => { role: "engineer" },// "marc" => { role: "designer" },// }map.toImmutable();
Returns
  • immutableStructureobject

    Returns a JavaScript object in the shape of your data structure. LiveMap is converted to a map, LiveObject to an object, and LiveList to an array.

Arguments
None

LiveList

The LiveList class represents an ordered collection of items that is synchronized across clients. To add typing, read more under typing Storage.

type Names = LiveList<string>;

Items can contain other Storage structures, or JSON-serializable data.

new LiveList

Create an empty LiveList.

const list = new LiveList();

Create a LiveList with initial data.

const list = new LiveList(["adrien", "jonathan"]);
Returns
  • LiveListLiveList<L>

    The newly created LiveList.

Arguments
  • initialValueArray<L extends LsonObject>Required

    The initial array of values for the LiveList. Can contain JSON-serializable data and other Liveblocks conflict-free data structures.

clear

Removes all the elements.

const list = new LiveList(["adrien", "jonathan"]);list.clear();
// []list.toImmutable();
Returns
Nothing
Arguments
None

delete

Deletes an element at the specified index. If the index doesn’t exist, an Error is thrown.

const list = new LiveList(["adrien", "jonathan"]);list.delete(1);
// ["adrien"]list.toImmutable();
Returns
Nothing
Arguments
  • indexnumberRequired

    The index of the property you’re deleting. If the property doesn’t exist, an Error is thrown.

every

Tests whether all elements pass the test implemented by the provided function. Returns true if the predicate function returns a truthy value for every element. Otherwise, false.

const list = new LiveList([0, 2, 4]);
// truelist.every((i) => i % 2 === 0);
list.push(5);
// falselist.every((i) => i % 2 === 0);
Returns
  • isEveryboolean

    Whether all elements pass the test implemented by the provided function.

Arguments
  • callback(value: L, index: number) => unknownRequired

    A function to execute for each item in the array. It should return a truthy value to indicate the element passes the test, and a falsy value otherwise. The function is passed the value of the item and its current index.

filter

Creates an array with all elements that pass the test implemented by the provided function.

const list = new LiveList([0, 1, 2, 3, 4]);
// [0, 2, 4]list.filter((i) => i % 2 === 0);
Returns
  • filteredArrayL[]

    An array containing each item of the LiveList that passed the test implemented by the provided function.

Arguments
  • callback(value: L, index: number) => unknownRequired

    A function to execute for each item in the array. It should return a truthy value to indicate the element passes the test, and a falsy value otherwise. The function is passed the value of the item and its current index.

find

Returns the first element that satisfies the provided testing function. If no item passes the test, undefined is returned.

const list = new LiveList(["apple", "lemon", "tomato"]);
// "lemon"list.find((value, index) => value.startsWith("l"));
Returns
  • itemL | undefined

    The item that has been found. If no item passes the test, undefined is returned.

Arguments
  • callback(value: L, index: number) => unknownRequired

    A function to execute for each item in the array. It should return a truthy value to indicate the element passes the test, and a falsy value otherwise. The function is passed the value of the item and its current index.

findIndex

Returns the index of the first element in the LiveList that satisfies the provided testing function. If no item passes the test, -1 is returned.

const list = new LiveList(["apple", "lemon", "tomato"]);
// 1list.findIndex((value, index) => value.startsWith("l"));
Returns
  • indexnumber

    The index of the item that has been found. If no item passes the test, -1 is returned.

Arguments
  • callback(value: L, index: number) => unknownRequired

    A function to execute for each item in the array. It should return a truthy value to indicate the element passes the test, and a falsy value otherwise. The function is passed the value of the item and its current index.

forEach

Executes a provided function once for each element.

const list = new LiveList(["adrien", "jonathan"]);
// "adrien", "jonathan"list.forEach((item) => console.log(item));
Returns
Nothing
Arguments
  • callbackRequired

    A callback for each item. The callback is passed the current value and index. Return values are ignored.

get

Get the element at the specified index. Returns undefined if the index doesn’t exist.

const list = new LiveList(["adrien", "jonathan"]);
// "jonathan"list.get(1);
Returns
  • itemL | undefined

    The value of the item at the index. Returns undefined if it doesn’t exist.

Arguments
  • indexnumberRequired

    The index of the item you’re getting.

indexOf

Returns the first index at which a given element can be found in the LiveList. Returns -1 if it is not present.

const list = new LiveList(["adrien", "jonathan"]);
// 1list.indexOf("jonathan");
// undefinedlist.indexOf("chris");
Returns
  • indexnumber

    The index of the item. Returns -1 if it doesn’t exist.

Arguments
  • searchElementLRequired

    The item you’re locating.

  • indexnumber

    The index to start the search at.

insert

Inserts one element at a specified index. Throws an Error if the index is out of bounds.

const list = new LiveList(["adrien", "jonathan"]);
list.insert("chris", 1);
// ["adrien", "chris", "jonathan"]list.toImmutable();
Returns
Nothing
Arguments
  • valueL extends LsonObjectRequired

    The value of the item you’re inserting.

  • indexnumberRequired

    The index to insert the item into.

lastIndexOf

Returns the last index at which a given element can be found in the LiveList, or -1 if it is not present. The LiveList is searched backwards, starting at fromIndex. Returns -1 if it is not present.

const list = new LiveList(["adrien", "jonathan", "adrien"]);
// 2list.indexOf("adrien");
// undefinedlist.indexOf("chris");
Returns
  • indexnumber

    The index of the item. Returns -1 if it doesn’t exist.

Arguments
  • searchElementLRequired

    The item you’re locating.

  • indexnumber

    The index at which to start searching backwards.

length

Returns the number of elements.

const list = new LiveList(["adrien", "jonathan"]);
// 3list.length; // equals
Returns
  • lengthnumber

    The number of items in the LiveList.

Arguments
  • N/A

map

Creates an array populated with the results of calling a provided function on every element.

const list = new LiveList(["apple", "lemon", "tomato"]);
// ["APPLE", "LEMON", "TOMATO"]list.map((value, index) => value.toUpperCase());
Returns
  • array

    The array of each item has been transformed by the callback function.

Arguments
  • callbackRequired

    A callback for each item. The callback is passed the current value and index. Return values are used in the returned array.

move

Moves one element at a specified index.

const list = new LiveList(["adrien", "chris", "jonathan"]);
list.move(2, 0);
// ["jonathan", "adrien", "chris"]list.toImmutable();
Returns
Nothing
Arguments
  • indexnumberRequired

    The index of the item to move.

  • targetIndexnumberRequired

    The index where the element should be after moving.

push

Adds one element to the end of the LiveList.

const list = new LiveList(["adrien", "jonathan"]);
list.push("chris");
// ["adrien", "jonathan", "chris"]list.toImmutable();
Returns
Nothing
Arguments
  • indexLRequired

    The item to add to the end of the LiveList.

set

Replace one element at the specified index.

const list = new LiveList(["adrien", "jonathan"]);
list.set(1, "chris");
// equals ["adrien", "chris"]list.toImmutable();

some

Tests whether at least one element in the LiveList passes the test implemented by the provided function.

const list = new LiveList(["apple", "lemon", "tomato"]);
// truelist.some((value, index) => value.startsWith("l"));
// falselist.some((value, index) => value.startsWith("x"));
Returns
  • areSomeboolean

    Whether any elements pass the test implemented by the provided function.

Arguments
  • callback(value: L, index: number) => unknownRequired

    A function to execute for each item in the array. It should return a truthy value to indicate the element passes the test, and a falsy value otherwise. The function is passed the value of the item and its current index.

clone

Returns a deep copy of the LiveList that can be inserted elsewhere in the Storage tree.

const list = new LiveList(/* ... */);root.set("a", list);root.set("b", list.clone());
Returns
  • clonedStructureLiveList

    The cloned LiveList.

Arguments
None

toImmutable

Returns an immutable JavaScript array that is equivalent to the LiveList. Nested values will also be immutable.

const list = new LiveList([  new LiveObject({ name: "Olivier" }),  new LiveObject({ name: "Vincent" }),]);
// [// { name: "Olivier" },// { name: "Vincent" },// ]list.toImmutable();
Returns
  • immutableStructureobject

    Returns a JavaScript object in the shape of your data structure.ListList is converted to an array, LiveObject to an object, and LiveMap to a map.

Arguments
None

toArray

Transforms the LiveList into a normal JavaScript array.

const list = new LiveList(["🦁", "🦊", "🐵"]);list.toArray();// ["🦁", "🦊", "🐵"]

Please note that this method won’t recursively convert Live structures, which may be surprising:

const list = new LiveList([  new LiveObject({ firstName: "Grace", lastName: "Hopper" }),]);list.toArray();// [ <LiveObject instance> ]  // ❗️

Utilities

getMentionedIdsFromCommentBody

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

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

Here’s an example with a custom CommentBody.

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

stringifyCommentBody

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

import { stringifyCommentBody } from "@liveblocks/client";
const stringComment = await stringifyCommentBody(comment.body);
// "Hello marc@example.com from https://liveblocks.io"console.log(stringComment);

A number of options are available.

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

Formatting examples

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

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

TypeScript

Typing your room

By passing Presence, Storage, UserMeta, and RoomEvents types to client.enterRoom, you can enable type hints on your room. Here are some example values.

import { LiveList } from "@/liveblocks/client";
// Each user’s Presencetype Presence = { cursor: { x: number; y: number };};
// The Storage tree for the roomtype Storage = { animals: LiveList<string>;};
// User information set when authenticating with a secret keytype UserMeta = { id: string; info: { // Custom properties, corresponds with userInfo };};
// Custom events that can be broadcast, use a union for multiple eventstype RoomEvent = { type: "REACTION"; emoji: "🔥";};
const { room, leave } = client.enterRoom< Presence, Storage, UserMeta, RoomEvent>("my-room-id", { initialPresence: {} });

You can also pass types to client.getRoom.

const { room, leave } = client.getRoom<Presence, Storage, UserMeta, RoomEvent>(  "my-room-id");

User

User is a type that’s returned by room.getSelf, room.getOthers, and other functions. Some of its values are set when typing your room, here are some example values:

// Each user’s Presencetype Presence = {  cursor: { x: number: y: number }}
type Storage = {}type RoomEvent = {}
// User information set when authenticating with a secret keytype UserMeta = { id: string; info: { // Custom `userInfo` properties set when authenticating avatar: string; }}
const { room, leave } = client.enterRoom< Presence, Storage, UserMeta, RoomEvent>("my-room-id", { initialPresence: {} });
// {// connectionId: 52,// presence: {// cursor: { x: 263, y: 786 },// },// id: "mislav.abha@example.com",// info: {// avatar: "/mislav.png",// },// canWrite: true,// canComment: true,// isReadOnly: false,// }const user = room.getSelf();
Properties
  • connectionIdnumber

    The connection ID of the User. It is unique and increments with every new connection.

  • idTUserMeta["id"]

    The ID of the User that has been set in the authentication endpoint. Useful to get additional information about the connected user.

  • infoTUserMeta["info"]

    Additional user information that has been set in the authentication endpoint.

  • presenceTPresence

    The user’s Presence data.

  • canWriteboolean

    True if the user can mutate the Room’s Storage and/or YDoc, false if they can only read but not mutate it.

  • canCommentboolean

    The ID of the User that has been set in the authentication endpoint. Useful to get additional information about the connected user.

  • isReadOnlybooleanDeprecated

    Deprecated. False if the user can mutate the Room’s Storage and/or YDoc, true if they can only read but not mutate it.