API Reference - @liveblocks/client

Create a client that will be responsible to communicate with Liveblocks servers.

When creating a client with a public key, you don’t need to setup an authorization endpoint:

1
2
3
4
5
import { createClient } from "@liveblocks/client";
const client = createClient({
publicApiKey: "pk_live_xxxxxxxxxxxxxxxxxxxxxxxx",
});

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

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

If you need to add additional headers or use your own function to call the endpoint, authEndpoint also supports a callback.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createClient } from "@liveblocks/client";
const client = createClient({
authEndpoint: async (room) => {
const response = await fetch("/api/auth", {
method: "POST",
headers: {
Authentication: "token",
"Content-Type": "application/json",
},
body: JSON.stringify({ room }),
});
return await response.json();
},
});

If you want to use @liveblocks/client with React Native, you need to add an atob polyfill.

As a polyfill, we recommend installing the package base-64.

npm install base-64

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

1
2
3
4
5
6
7
8
9
import { createClient } from "@liveblocks/client";
import { decode } from "base-64";
const client = createClient({
/* ... other options ... */
polyfills: {
atob: decode,
},
});

If you want to use @liveblocks/client in a node environment, you need to provide WebSocket and fetch polyfills.

As polyfills, we recommend installing the packages ws and node-fetch.

npm install ws node-fetch

Then, pass them to the createClient function as below.

1
2
3
4
5
6
7
8
9
10
11
import { createClient } from "@liveblocks/client";
import fetch from "node-fetch";
import WebSocket from "ws";
const client = createClient({
/* ... other options ... */
polyfills: {
fetch,
WebSocket,
},
});

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

By default, the client throttle the WebSocket messages sent to 100 milliseconds.

It is possible to override that configuration with the throttle option.

throttle should be between 80 and 1000 milliseconds.

1
2
3
4
5
6
import { createClient } from "@liveblocks/client";
const client = createClient({
/* ... other options ... */
throttle: 80,
});

Client returned by createClient.

Enters a room and returns it. The authentication endpoint is called as soon as you call this function.

The second argument is an optional configuration for the presence and storage.

  • initialPresence - The initial Presence to use for the User currently entering the Room. Presence data belongs to the current User and is readable to all other Users in the room while the current User is connected to the Room. Must be serializable to JSON.
  • initialStorage - The initial Storage structure to create when a new Room is entered for the first time. Storage data is shared and belongs to the Room itself. It persists even after all Users leave the room, and is mutable by every client. Must either contain Live structures (e.g. new LiveList(), new LiveObject({"{"} a: 1 {"}"}), etc.) or be serializable to JSON.
1
2
3
4
const room = client.enter("my-room", {
initialPresence: { cursor: null },
initialStorage: { todos: new LiveList() },
});

Leaves a room.

Gets a room by id. Returns null if client.enter has not been called previously.

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

Room returned by client.enter or client.getRoom.

Gets the presence of the current user.

const presence = room.getPresence();

Updates the presence of the current user. Only pass the properties you want to update. No need to send the full presence.

1
2
3
4
5
room.updatePresence({ x: 0 });
room.updatePresence({ y: 0 });
const presence = room.getPresence();
// presence is equivalent to { x: 0, y: 0 }

updatePresence accepts an optional argument to add a new item to the undo/redo stack. See room.history for more information.

1
2
3
4
room.updatePresence({ selectedId: "xxx" }, { addToHistory: true });
room.updatePresence({ selectedId: "yyy" }, { addToHistory: true });
room.history.undo();
// room.getPresence() equals { selectedId: "xxx" }

Gets all the other users in the Room.

const others = room.getOthers();

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

By default, broadcasting an event acts as a “fire and forget”. If the user is not currently in the room, the event is simply discarded. With the option shouldQueueEventIfNotReady , the event will be queued and sent once the connection is established.

❗ We are not sure if we want to support this option in the future so it might be deprecated to be replaced by something else

1
2
3
4
5
6
7
8
9
// On client A
room.broadcastEvent({ type: "EMOJI", emoji: "🔥" });
// On client B
room.subscribe("event", ({ event }) => {
if (event.type === "EMOJI") {
// Do something
}
});

Gets the current user. Returns null if it is not yet connected to the room.

const { connectionId, presence, id, info } = room.getSelf();

Subscribe to updates for a particular storage item.

Takes a callback that is called when the storage item is updated.

Returns an unsubscribe function.

1
2
3
4
const { root } = await room.getStorage();
const unsubscribe = room.subscribe(root, (root) => {
// Do something
});

It’s also possible to subscribe to a storage item and its children by passing an optional isDeep parameter. In that case, the callback will get called with a list of updates instead. Each such update is a { type, node } object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const { root } = await room.getStorage();
const unsubscribe = room.subscribe(
root,
(updates) => {
for (const update of updates) {
switch (update.type) {
case "LiveObject": {
// update.node is the LiveObject that has been updated
break;
}
case "LiveMap": {
// update.node is the LiveMap that has been updated
break;
}
case "LiveList": {
// update.node is the LiveList that has been updated
break;
}
}
}
},
{ isDeep: true }
);

Subscribe to events broadcasted by Room.broadcastEvent.

Takes a callback that is called when a user calls Room.broadcastEvent.

Returns an unsubscribe function.

1
2
3
const unsubscribe = room.subscribe("event", ({ event, connectionId }) => {
// Do something
});

Subscribe to the current user presence updates.

Takes a callback that is called every time the current user presence is updated with Room.updatePresence.

Returns an unsubscribe function.

1
2
3
const unsubscribe = room.subscribe("my-presence", (presence) => {
// Do something
});

Subscribe to the other users updates.

Takes a callback that is called when a user enters or leaves the room or when a user update its presence.

Returns an unsubscribe function.

1
2
3
const unsubscribe = room.subscribe("others", (others) => {
// Do something
});

Subscribe to the websocket connection status updates.

Takes a callback that is called when the connection status changes.

The value can be : authenticating, connecting, open, failed, closed or unavailable.

Returns an unsubscribe function.

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

Subscribe to potential room connection errors.

Returns an unsubscribe function.

1
2
3
4
5
const unsubscribe = room.subscribe("error", (error) => {
if (error.code === 4005) {
// Maximum concurrent connections per room exceeded.
}
});

Batches modifications made during the given function.

All the modifications are sent to other clients in a single message.

All the subscribers are called only after the batch is over.

All the modifications are merged in a single history item (undo/redo).

1
2
3
4
5
const { root } = await room.getStorage();
room.batch(() => {
root.set("x", 0);
room.updatePresence({ cursor: { x: 100, y: 100 } });
});

Room’s history contains functions that let you undo and redo operation made on by the current client on the presence and storage.

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

Undoes the last operation executed by the current client. It does not impact operations made by other clients.

1
2
3
4
room.updatePresence({ selectedId: "xxx" }, { addToHistory: true });
room.updatePresence({ selectedId: "yyy" }, { addToHistory: true });
room.history.undo();
// room.getPresence() equals { selectedId: "xxx" }

Redoes the last operation executed by the current client. It does not impact operations made by other clients.

1
2
3
4
5
6
room.updatePresence({ selectedId: "xxx" }, { addToHistory: true });
room.updatePresence({ selectedId: "yyy" }, { addToHistory: true });
room.history.undo();
// room.getPresence() equals { selectedId: "xxx" }
room.history.redo();
// room.getPresence() equals { selectedId: "yyy" }

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

1
2
3
4
5
6
7
room.updatePresence({ cursor: { x: 0, y: 0 } }, { addToHistory: true });
room.history.pause();
room.updatePresence({ cursor: { x: 1, y: 1 } }, { addToHistory: true });
room.updatePresence({ cursor: { x: 2, y: 2 } }, { addToHistory: true });
room.history.resume();
room.history.undo();
// room.getPresence() equals { cursor: { x: 0, y: 0 } }

Resumes history. Modifications made on the Room are not merged into a single history item anymore.

1
2
3
4
5
6
7
room.updatePresence({ cursor: { x: 0, y: 0 } }, { addToHistory: true });
room.history.pause();
room.updatePresence({ cursor: { x: 1, y: 1 } }, { addToHistory: true });
room.updatePresence({ cursor: { x: 2, y: 2 } }, { addToHistory: true });
room.history.resume();
room.history.undo();
// room.getPresence() equals { cursor: { x: 0, y: 0 } }

The storage block is in beta

The following APIs are subject to change during the beta.

The room’s storage is a conflicts-free state that multiple users can edit at the same time. It persists even after everyone leaves the room. Liveblocks provides 3 data structures that can be nested to create the state that you want.

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

Get the room’s storage asynchronously (returns a Promise). The storage’s root is a LiveObject.

const { root } = await room.getStorage();

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 a name (string) and an age (number) field.

Keys should be strings, and values should be serializable to JSON.

If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.

Create an empty LiveObject

const object = new LiveObject();

Create a LiveObject with initial data.

const object = new LiveObject({ firstName: "Margaret", lastName: "Hamilton" });

Delete a property from the LiveObject

1
2
3
const object = new LiveObject({ firstName: "Ada", lastName: "Lovelace" });
object.delete("lastName");
object.toObject(); // equals to { firstName: "Ada" }

Get a property from the LiveObject

1
2
const object = new LiveObject({ firstName: "Ada", lastName: "Lovelace" });
object.get("firstName"); // equals to "Ada"

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

1
2
const object = new LiveObject({ firstName: "Marie" });
object.set("lastName", "Curie");

Deprecated in 0.13

For more information, see the following release notes

Subscribes to updates. In a future version, you will be able to know exactly what changed.

1
2
3
4
5
const object = new LiveObject({ firstName: "Grace", lastName: "Hopper" });
object.subscribe(() => {
// Executed when the object is updated
});

Deprecated in 0.13

For more information, see the following release notes

Subscribes to updates and children updates. In a future version, you will be able to know exactly what changed.

1
2
3
4
5
6
7
8
9
10
const scientist = new LiveObject({ firstName: "Grace", lastName: "Hopper" });
const awards = new LiveList();
scientist.set("awards", awards);
scientist.subscribeDeep(() => {
// Executed when awards is updated
});
awards.push("Presidential Medal of Freedom");

Transform the LiveObject into a JavaScript object.

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

Adds or updates multiple properties at once.

1
2
const object = new LiveObject({ firstName: "Grace", lastName: "Hopper" });
object.update({ job: "Computer Scientist", birthDate: "December 9, 1906" });

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. Keys should be strings, and values should be serializable to JSON. If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.

Create an empty LiveMap.

const map = new LiveMap();

Create a LiveMap with initial data.

1
2
3
4
const map = new LiveMap([
["keyA", "valueA"],
["keyB", "valueB"],
]);

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

1
2
3
4
5
6
7
const map = new LiveMap([
["keyA", "valueA"],
["keyB", "valueB"],
]);
map.delete("keyA"); // equals true
map.get("keyA"); // equals undefined
map.delete("unknownKey"); // equals false

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

1
2
3
for (const [key, value] of map.entries()) {
// Iterate over all the keys and values of the map
}
Iteration with TypeScript

If your TypeScript project targets "es5" or lower, you’ll need to enable the --downlevelIteration option to use this API.

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

1
2
3
4
5
6
const map = new LiveMap([
["keyA", "valueA"],
["keyB", "valueB"],
]);
map.forEach((value, key) => console.log(value));
// prints to the console "valueA", "valueB"

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

1
2
3
4
5
6
const map = new LiveMap([
["keyA", "valueA"],
["keyB", "valueB"],
]);
map.get("keyA"); // equals "valueA"
map.get("unknownKey"); // equals undefined

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

1
2
3
4
5
6
const map = new LiveMap([
["keyA", "valueA"],
["keyB", "valueB"],
]);
map.has("keyA"); // equals true
map.has("unknownKey"); // equals false

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

1
2
3
for (const key of map.keys()) {
// Iterate over all the keys and values of the map
}
Iteration with TypeScript

If your TypeScript project targets "es5" or lower, you’ll need to enable the --downlevelIteration option to use this API.

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

1
2
const map = new LiveMap();
map.set("keyA", "valueA");

Returns the number of elements in the LiveMap.

1
2
3
4
5
const map = new LiveMap([
["keyA", "valueA"],
["keyB", "valueB"],
]);
map.size; // equals 2

Deprecated in 0.13

For more information, see the following release notes

Subscribes to updates. In a future version, you will be able to know exactly what changed.

1
2
3
4
5
const map = new LiveMap();
map.subscribe(() => {
// Executed when the map is updated
});

Deprecated in 0.13

For more information, see the following release notes

Subscribes to updates and children updates. In a future version, you will be able to know exactly what changed.

1
2
3
4
5
6
7
8
9
10
const map = new LiveMap();
const mathematician = new LiveObject({ firstName: "Ada" });
map.set("id", mathematician);
map.subscribeDeep(() => {
// Executed when mathematician is updated
});
mathematician.set("lastName", "Lovelace");

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

1
2
3
for (const value of map.values()) {
// Iterate over all the values of the map
}
Iteration with TypeScript

If your TypeScript project targets "es5" or lower, you’ll need to enable the --downlevelIteration option to use this API.

The LiveList class represents an ordered collection of items that is synchorinized across clients. Items should be serializable to JSON or another Live data structure.

Create an empty LiveList

const list = new LiveList();

Create a LiveList with initial data

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

Removes all the elements.

1
2
3
const list = new LiveList(["🦁", "🦊", "🐵"]);
list.clear();
list.toArray(); // equals []

Deletes an element at the specified index.

1
2
3
const list = new LiveList(["🦁", "🦊", "🐵"]);
list.delete(1);
list.toArray(); // equals ["🦁", "🐵"]

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.

1
2
3
4
const list = new LiveList([0, 2, 4]);
list.every((i) => i % 2 === 0); // equals true
list.push(5);
list.every((i) => i % 2 === 0); // equals false

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

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

Returns the first element that satisfies the provided testing function.

1
2
const list = new LiveList(["apple", "lemon", "tomato"]);
list.find((fruit) => fruit.startsWith("l")); // equals "lemon"

Returns the index of the first element in the LiveList that satisfies the provided testing function.

1
2
const list = new LiveList(["apple", "lemon", "tomato"]);
list.findIndex((fruit) => fruit.startsWith("l")); // equals 1

Executes a provided function once for each element.

1
2
const list = new LiveList(["🦁", "🦊", "🐵"]);
list.forEach((item) => console.log(item)); // prints to the console "🦁", "🦊", "🐵"

Get the element at the specified index.

1
2
const list = new LiveList(["🦁", "🦊", "🐵"]);
list.get(2); // equals "🐵"

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

1
2
3
const list = new LiveList(["🦁", "🦊", "🐵"]);
list.indexOf("🐵"); // equals 2
list.indexOf("🐺"); // equals -1

Inserts one element at a specified index.

1
2
3
const list = new LiveList(["🦁", "🦊", "🐵"]);
list.insert("🐺", 1);
list.toArray(); // equals ["🦁", "🐺", "🦊", "🐵"]

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.

1
2
3
const list = new LiveList(["🦁", "🦊", "🐵", "🦊"]);
list.lastIndexOf("🦊"); // equals 3
list.lastIndexOf("🐺"); // equals -1

Returns the number of elements.

1
2
const list = new LiveList(["🦁", "🦊", "🐵"]);
list.length; // equals 3

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

1
2
const list = new LiveList(["apple", "lemon", "tomato"]);
list.map((fruit) => fruit.toUpperCase()); // equals ["APPLE", "LEMON", "TOMATO"]

Moves one element at a specified index.

1
2
3
const list = new LiveList(["🦁", "🦊", "🐵"]);
list.move(2, 0); // move the "🐵" at index 0
list.toArray(); // equals ["🐵", "🦁", "🦊"]

Adds one element to the end of the LiveList.

1
2
3
const list = new LiveList(["🦁", "🦊", "🐵"]);
list.push("🐺");
list.toArray(); // equals ["🦁", "🦊", "🐵", "🐺"]

Replace one element at the specified index.

1
2
3
const list = new LiveList(["🦁", "🦊", "🐵"]);
list.set(1, "🐺");
list.toArray(); // equals ["🦁", "🐺", "🐵"]

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

1
2
3
const list = new LiveList(["apple", "lemon", "tomato"]);
list.some((fruit) => fruit.startsWith("l")); // equals true
list.some((fruit) => fruit.startsWith("x")); // equals false

Deprecated in 0.13

For more information, see the following release notes

Subscribes to updates. In a future version, you will be able to know exactly what changed.

1
2
3
4
5
const list = new LiveList(["apple", "lemon", "tomato"]);
list.subscribe(() => {
// Executed when the list is updated
});

Deprecated in 0.13

For more information, see the following release notes

Subscribes to updates and children updates. In a future version, you will be able to know exactly what changed.

1
2
3
4
5
6
7
8
9
10
const authors = new LiveList();
const mathematician = new LiveObject({ firstName: "Ada" });
authors.push(mathematician);
authors.subscribeDeep(() => {
// Executed when mathematician is updated
});
mathematician.set("lastName", "Lovelace");

Returns an Array of all the elements in the LiveList.

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