Sign in

Server-side editing

Sync data can be modified from your back end, using the same conflict resolution that enables multiplayer collaboration on the front end. This allows you to create initial room data, schedule jobs, run migrations, enable agentic editing, and more.

Server-side Sync APIs

There are two different ways to modify Sync data from your back end, using methods in Node.js or using JSON Patch from any language.

Example document shape

In these snippets, we’ll use the following document type declaration as an example, a simple canvas with a LiveList of LiveObject shapes

liveblocks.config.ts
import { LiveList, LiveObject } from "@liveblocks/core";
type Shape = LiveObject<{ x: number; y: number; color: string;}>;
declare global { interface Liveblocks { type Storage = { shapes: LiveList<Shape>; } }}

Edit Sync data with Node.js methods

Use Liveblocks.mutateStorage to read and modify Sync data with the same conflict-free data types used by the front end. For example, to add a new shape from the server, get the shapes list with LiveObject.get, and push a new shape with LiveList.push. It may be helpful to check that the list exists before creating it if your server process will run before setting initial Storage values.

import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: "",});
await liveblocks.mutateStorage("my-room", ({ root }) => { let shapes = root.get("shapes");
if (!shapes) { shapes = new LiveList([]); root.set("shapes", shapes); }
const newShape = new LiveObject({ x: 50, y: 100, color: "red", });
shapes.push(newShape);});

The callback receives the latest document and flushes its changes back to the room. Clients update as those changes are persisted. The callback can be asynchronous, but changes made before an await may synchronize before later work completes, so do not treat it as a database transaction.

Edit multiple rooms

It’s also possible to edit multiple rooms at once with Liveblocks.massMutateStorage. This is particularly helpful for running data migrations—in this snippet, if a room has no schema version, it’s given a value, and a new layers LiveList is created. 5 rooms are processed concurrently.

import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: "",});
await liveblocks.massMutateStorage( { query: { roomId: { startsWith: "project:" } } }, ({ root }) => { if (!root.get("schemaVersion")) { root.set("schemaVersion", "2.0.0"); root.set("layers", new LiveList([])); } }, { concurrency: 5 });

Edit Sync data with JSON Patch

Use the JSON Patch endpoint to modify Sync data from any language, and from services that are familiar with JSON Patch operations such as AI agents. For example, to add a new shape from the server, use an add operation with the /shapes/- path, which appends the value to the end of the shapes list.

PATCH /v2/rooms/my-room/storage/json-patchContent-Type: application/json
[ { "op": "add", "path": "/shapes/-", "value": { "x": 50, "y": 100, "color": "red" } }]

JSON Patch is also available in our Python SDK, named patch_storage_document.

Server-side integration APIs

A number of Liveblocks integrations have server-side APIs that allow you to modify Sync data using the integration’s document model, instead of the Sync model.

React Flow

You can edit React Flow documents from the server using mutateFlow. In this example, a new node and a new edge are added to the flow.

import { Liveblocks } from "@liveblocks/node";import { mutateFlow } from "@liveblocks/react-flow/node";
const client = new Liveblocks({ secret: "",});
await mutateFlow({ client, roomId: "my-room-id" }, (flow) => { flow.addNode({ id: "2", position: { x: 50, y: 1000 }, data: { label: "Hello world" }, }); flow.addEdge({ id: "e1-2", source: "1", target: "2" });});

ProseMirror, Tiptap, and BlockNote

When using Yjs-backed documents, you can edit ProseMirror, Tiptap, and BlockNote text documents from the server using withProsemirrorDocument. In this example, new text is inserted into the document.

import { Liveblocks } from "@liveblocks/node";import { withProsemirrorDocument } from "@liveblocks/node-prosemirror";
const client = new Liveblocks({ secret: "",});
await withProsemirrorDocument( { client, roomId: "test-room", }, async (api) => { await api.update((doc, tr) => { return tr.insertText("Hello world"); }); });

Edit Storage-backed documents using Liveblocks.mutateStorage instead.

Lexical

When using Yjs-backed documents, you can edit Lexical text content from the server using withLexicalDocument. In this example, the document is updated with a new paragraph containing a text node.

import { Liveblocks } from "@liveblocks/node";import { withLexicalDocument } from "@liveblocks/node-lexical";import { $getRoot } from "lexical";import { $createParagraphNode, $createTextNode } from "lexical/nodes";
const client = new Liveblocks({ secret: "",});
await withLexicalDocument( { roomId: "my-room-id", client: liveblocks }, async (doc) => { await doc.update(() => { const root = $getRoot(); const paragraphNode = $createParagraphNode(); const textNode = $createTextNode("Hello world"); paragraphNode.append(textNode); root.append(paragraphNode); }); });

Edit Storage-backed documents using Liveblocks.mutateStorage instead.