Sign in

Sync engine - Liveblocks Storage

Liveblocks Storage is a realtime sync engine designed for multiplayer creative tools such as Figma, Pitch, and Spline. LiveList, LiveMap, and LiveObject conflict-free data types can be used to build all sorts of multiplayer tools. Liveblocks permanently stores Storage data in each room, handling scaling and maintenance for you.

Interactive tutorial

Our interactive React tutorial guides you through using each of the concepts listed on this page.

Presence

Easily create online presence indicators such as online avatar stacks and live cursors, with hooks like useOthers, which returns an array of currently connected users in the room, and useSelf which returns your own data.

import { useOthers, useSelf } from "@liveblocks/react/suspense";
function AvatarStack() { const others = useOthers(); const self = useSelf();
return ( <div> {others.map(({ connectionId, info }) => ( <img key={connectionId} src={info.avatar} /> ))} <img src={self.info.avatar} /> </div> );}

User info is set when authenticating with a secret key.

Conflict-free data structures

Create realtime document state such as shapes on a canvas, notes on a whiteboard, or cells in a spreadsheet, with our permanent CRDT-like data structures. useStorage returns data structures as simple JSON, and useMutation allows you to modify the data structures themselves.

import { useStorage, useMutation } from "@liveblocks/react/suspense";
function MultiplayerCanvas() { const shapes = useStorage((root) => root.shapes);
const handleShapeDrag = useMutation(({ storage }, shapeId, { x, y }) => { const shape = storage.get("shapes").get(shapeId); shape.update({ x, y }); }, []);
return ( <div> {shapes.map((shape) => ( <DraggableShape key={shape.id} shape={shape} onDrag={handleShapeDrag} /> ))} </div> );}

Conflict-free data structures use LiveObject, LiveList, and LiveMap under the hood, and will automatically resolve conflicts when multiple users modify the same data at the same time. Additionally, they can be nested to create complex data structures and are automatically typed throughout your app.

Manage document history

Multiplayer undo and redo is notoriously tough to implement which is why we provide simple hooks like useUndo and useCanUndo to handle it for you.

import {  useStorage,  useMutation,  useUndo,  useCanUndo,} from "@liveblocks/react/suspense";
function MultiplayerCanvas() { const undo = useUndo(); const canUndo = useCanUndo();
const shapes = useStorage((root) => root.shapes);
const handleShapeDrag = useMutation(({ storage }, shapeId, { x, y }) => { const shape = storage.get("shapes").get(shapeId); shape.update({ x, y }); }, []);
return ( <div> {canUndo ?? <button onClick={undo}>Undo</button>} {shapes.map((shape) => ( <DraggableShape key={shape.id} shape={shape} onDrag={handleShapeDrag} /> ))} </div> );}

We also provide useRedo and useCanRedo to handle redo.

Broadcast

Broadcast realtime events to other clients, helpful for triggering live actions such as playing a video, or for revalidating your database’s data after a change. useBroadcastEvent sends events, and useEventListener listens for them.

import {  useBroadcastEvent,  useEventListener,} from "@liveblocks/react/suspense";
function VideoPlayer() { const [playing, setPlaying] = useState(false); const broadcast = useBroadcastEvent();
useEventListener(({ event }) => { if (event.type === "PLAY_VIDEO") { setPlaying(true); } });
return ( <div> <button onClick={() => broadcast({ type: "PLAY_VIDEO" })}> ▶️ Play Video </button> <Video playing={playing} /> </div> );}

Handle connection status

We’ve designed a number of hooks to help you handle different network conditions, such as useLostConnectionListener which helps you render UI when user’s have connection loss.

import { toast } from "my-preferred-toast-library";import { useLostConnectionListener } from "@liveblocks/react/suspense";
function App() { useLostConnectionListener((event) => { if (event === "lost") { return toast.warn("Trying to reconnect…"); }
if (event === "restored") { return toast.success("Successfully reconnected!"); } });
// ...}

Sync status

useSyncStatus is a related hook which allows you to render the current sync state in your app, before a change has been saved.

import { useSyncStatus } from "@liveblocks/react/suspense";
function StorageStatusBadge() { const syncStatus = useSyncStatus({ smooth: true });
return <div>{syncStatus === "synchronized" ? "✅ Saved" : "🔄 Saving"}</div>;}

Other hooks

A number of other hooks are available to help you with different aspects of multiplayer development, check our API reference for more information.

Server-side modifications

Storage’s presence and conflict-free data structures can be modified through our Node.js package or via REST API.

Presence

Presence can be modified with liveblocks.setPresence, allowing you set an ephemeral value that will expire after a certain amount of time.

await liveblocks.setPresence("my-room-id", {  userId: "agent-123",  data: {    status: "active",    cursor: { x: 100, y: 200 },  },  userInfo: {    name: "AI Assistant",    avatar: "https://example.com/avatar.png",  },  ttl: 60,});

The same operation can be performed in other languages with the Set ephemeral presence REST API.\

Conflict-free data structures

Conflict-free data structures can be modified with liveblocks.mutateStorage, allowing you to modify the data structures similarly to on the client-side.

await liveblocks.mutateStorage(  "my-room-id",
({ root }) => { root.get("list").push("item3"); });

The same operation can be performed using the Apply JSON Patch to Storage REST API. We have a guide on Modifying Storage via REST API with JSON Patch that covers this in more detail.

Broadcast

Broadcast can be performed with liveblocks.broadcastEvent, allowing you to send events to all connected clients.

await liveblocks.broadcastEvent("my-room-id", { type: "PLAY_VIDEO" });

The same operation can be performed using the Broadcast event to a room REST API.

API Reference

Presence

Broadcast

Storage

Examples using Liveblocks Storage