Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,72 @@ function StorageStatusBadge() {
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鈥檚 presence and conflict-free data structures can be modified through our
[Node.js package](/docs/api-reference/liveblocks-node) or via
[REST API](/docs/api-reference/rest-api-endpoints).

### Presence [#server-side-modifications-presence]

Presence can be modified with
[`liveblocks.setPresence`](/docs/api-reference/liveblocks-node#set-rooms-roomId-presence),
allowing you set an ephemeral value that will expire after a certain amount of
time.

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

The same operation can be performed in other languages with the
[Set ephemeral presence REST API](/docs/api-reference/rest-api-endpoints#post-rooms-roomId-presence).\

### Conflict-free data structures [#server-side-modifications-conflict-free-data-structures]

Conflict-free data structures can be modified with
[`liveblocks.mutateStorage`](/docs/api-reference/liveblocks-node#mutate-storage),
allowing you to modify the data structures similarly to on the client-side.

```ts
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](/docs/api-reference/rest-api-endpoints#patch-rooms-roomId-storage-json-patch).
We have a guide on
[Modifying Storage via REST API with JSON Patch](/docs/guides/modifying-storage-via-rest-api-with-json-patch)
that covers this in more detail.

### Broadcast [#server-side-modifications-broadcast]

Broadcast can be performed with
[`liveblocks.broadcastEvent`](/docs/api-reference/liveblocks-node#post-broadcast-event),
allowing you to send events to all connected clients.

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

The same operation can be performed using the
[Broadcast event to a room REST API](/docs/api-reference/rest-api-endpoints#post-broadcast-event).

## API Reference

### Presence
Expand Down
116 changes: 116 additions & 0 deletions docs/references/v2.openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,122 @@
"description": "This endpoint deletes all of the room鈥檚 Storage data. Calling this endpoint will disconnect all users from the room if there are any. Corresponds to [`liveblocks.deleteStorageDocument`](/docs/api-reference/liveblocks-node#delete-rooms-roomId-storage).\n"
}
},
"/rooms/{roomId}/storage/json-patch": {
"patch": {
"summary": "Apply JSON Patch to Storage",
"description": "Applies a sequence of [JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) operations to the room's Storage document, useful for modifying Storage. Operations are applied in order; if any operation fails, the document is not changed and a 422 response with a helpful message is returned.\n\n**Paths and data types:** Be as specific as possible with your target path. Every parent in the chain of path segments must be a LiveObject, LiveList, or LiveMap. Complex nested objects passed in `add` or `replace` operations are automatically converted to LiveObjects and LiveLists.\n\n**Performance:** For large Storage documents, applying a patch can be expensive because the full state is reconstructed on the server to apply the operations. Very large documents may not be suitable for this endpoint.\n\nFor a **full guide with examples**, see [Modifying storage via REST API with JSON Patch](/docs/guides/modifying-storage-via-rest-api-with-json-patch).",
"tags": ["Storage"],
"operationId": "patch-rooms-roomId-storage-json-patch",
"parameters": [
{
"schema": {
"type": "string"
},
"name": "roomId",
"in": "path",
"required": true,
"description": "ID of the room"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"required": ["op", "path"],
"properties": {
"op": {
"type": "string",
"enum": [
"add",
"remove",
"replace",
"move",
"copy",
"test"
],
"description": "The operation to perform (RFC 6902)."
},
"path": {
"type": "string",
"description": "A JSON Pointer to the target location (RFC 6901). Must start with \"/\"."
},
"from": {
"type": "string",
"description": "Required for \"move\" and \"copy\". A JSON Pointer to the source location."
},
"value": {
"description": "Required for \"add\", \"replace\", and \"test\". The value to add, the replacement value, or the value to test against."
}
},
"additionalProperties": true
}
},
"examples": {
"addAndRemove": {
"summary": "Add and remove",
"value": [
{ "op": "add", "path": "/score", "value": 42 },
{ "op": "remove", "path": "/oldKey" }
]
},
"appendToList": {
"summary": "Append to LiveList",
"value": [
{ "op": "add", "path": "/layers/-", "value": "newLayer" }
]
}
}
}
}
},
"responses": {
"200": {
"description": "Success. All operations were applied; the Storage document was updated."
},
"401": {
"$ref": "#/components/responses/401"
},
"403": {
"$ref": "#/components/responses/403"
},
"404": {
"$ref": "#/components/responses/404"
},
"422": {
"description": "Patch failed. The document was not changed. The response body includes an error code and a helpful message (e.g. invalid path, test failed, index out of bounds).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"examples": {
"INVALID_STORAGE_MUTATION_REQUEST": {
"value": {
"error": "INVALID_STORAGE_MUTATION_REQUEST",
"message": "Test failed: value at path \"/name\" does not match.",
"suggestion": "Ensure the test value matches the current document value.",
"docs": ""
}
},
"UNPROCESSABLE_ENTITY": {
"value": {
"error": "UNPROCESSABLE_ENTITY",
"message": "Invalid JSON pointer: path must start with \"/\".",
"suggestion": "Please ensure the data follows the correct format.",
"docs": ""
}
}
}
}
}
}
}
}
},
"/rooms/{roomId}/ydoc": {
"get": {
"summary": "Get Yjs document",
Expand Down
7 changes: 7 additions & 0 deletions guides/guides.json
Original file line number Diff line number Diff line change
Expand Up @@ -489,5 +489,12 @@
"topics": ["tutorials", "testing"],
"technologies": ["nextjs", "react"],
"date": "2026-02-17"
},
{
"title": "Modifying Storage via REST API with JSON Patch",
"path": "/modifying-storage-via-rest-api-with-json-patch",
"topics": ["rest-api", "storage"],
"technologies": ["storage"],
"date": "2026-02-20"
}
]
186 changes: 186 additions & 0 deletions guides/pages/modifying-storage-via-rest-api-with-json-patch.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
---
meta:
title: "Modifying Storage via REST API with JSON Patch"
parentTitle: "Guides"
description:
"Use the JSON Patch HTTP API to modify Liveblocks Storage from any language."
---

The
[Storage JSON Patch endpoint](/docs/api-reference/rest-api-endpoints#patch-rooms-roomId-storage-json-patch)
allows you to apply partial updates to a room's
[Storage](/docs/ready-made-features/multiplayer/sync-engine/liveblocks-storage)
document over HTTP. You send an array of operations (add, remove, replace, move,
copy, test) and the server applies them in order. This is especially useful for
modifying Storage from languages that don't yet have a native Liveblocks client,
for example, such as Python, so you can still read and update Storage using the
REST API.

## Format

The format follows the
[JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) specification (RFC
6902). Paths use [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901)
(RFC 6901).

```shell title="Endpoint"
PATCH https://api.liveblocks.io/v2/rooms/{roomId}/storage/json-patch
```

Authenticate with your project's **secret key** in the `Authorization` header.

```shell
"Authorization: Bearer {{SECRET_KEY}}"
```

Never expose your secret key in client-side code.

## Liveblocks data types and paths

Storage is a tree of Liveblocks types:
[LiveObject](/docs/api-reference/liveblocks-client#LiveObject),
[LiveList](/docs/api-reference/liveblocks-client#LiveList), and
[LiveMap](/docs/api-reference/liveblocks-client#LiveMap). When building your
patch:

- **Be as specific as possible** with your target path.
- **Every parent** in the chain of path segments must be a LiveObject, LiveList,
or LiveMap. You cannot target or traverse through plain JSON objects that
aren't one of these types.
- **Nested values in `add` and `replace`:** If you pass a complex nested object
or array as the `value` for an `add` or `replace` operation, it is
automatically converted into LiveObjects and LiveLists on the server.

## Operations and examples

The request body is a JSON array of operation objects. Each object has an `op`
field and a `path` field; some operations also require `value` or `from`.

### add

Adds a value at the given path. For an object/key, creates or replaces the
member. For a list, inserts at the given index.

- **Required:** `path`, `value`
- Use **`-`** as the last path segment to **append to the end of a LiveList**
(e.g. `"/layers/-"`). This is defined in RFC 6902.

```json
[
{ "op": "add", "path": "/score", "value": 42 },
{ "op": "add", "path": "/layers/-", "value": "newLayer" }
]
```

### remove

Removes the value at the given path.

- **Required:** `path`

```json
[{ "op": "remove", "path": "/oldKey" }]
```

### replace

Replaces the value at the given path. The target must exist.

- **Required:** `path`, `value`

```json
[{ "op": "replace", "path": "/score", "value": 100 }]
```

### move

Removes the value at `from` and adds it at `path`. The `from` location must
exist. Per RFC 6902, `from` must not be a prefix of `path` (you cannot move a
node into one of its children).

- **Required:** `from`, `path`

```json
[{ "op": "move", "from": "/name", "path": "/username" }]
```

### copy

Copies the value at `from` to `path`. The `from` location must exist.

- **Required:** `from`, `path`

```json
[{ "op": "copy", "from": "/template", "path": "/current" }]
```

### test

Checks that the value at `path` equals `value`. If it doesn't, the whole patch
fails and no changes are applied. Use `test` to guard against concurrent updates
(e.g. "only replace if the counter is still 5").

- **Required:** `path`, `value`

```json
[
{ "op": "test", "path": "/version", "value": 1 },
{ "op": "replace", "path": "/version", "value": 2 }
]
```

## The test operation

The **test** operation is a guard: it succeeds only when the value at the given
path is equal to the provided value (same type and value). If the test fails,
the server returns 422 and **does not apply any part of the patch**. This lets
you implement optimistic checks, for example, only increment a counter if it
still has the value you read earlier.

_note:_ test order matters. A test after a change will take into the account the
changes made by prior operations.

## Atomicity

**If any operation in the patch fails, the entire document is left unchanged.**
Operations are applied in order; as soon as one fails (e.g. invalid path, test
failure, index out of bounds), the server stops and returns an error. No partial
updates are applied.

## Error handling

When a patch fails, the server responds with **422 Unprocessable Entity**. The
response body follows the standard API error shape and includes:

- **error**: Error code (e.g. `INVALID_STORAGE_MUTATION_REQUEST`,
`UNPROCESSABLE_ENTITY`)
- **message**: Human-readable description of what went wrong
- **suggestion**: Optional hint on how to fix the request
- **docs**: Optional link to documentation

Use these fields to show helpful feedback and retry or correct the request.

## Performance and document size

To apply a patch, the server must **reconstruct the full Storage state** for the
room. For large Storage documents, this can be expensive in time and resources.
The JSON Patch endpoint may not be suitable for very large documents; prefer the
native client SDKs when you need to update large or frequently changing Storage
from a supported environment.

## Example: cURL

```bash
curl -X PATCH "https://api.liveblocks.io/v2/rooms/my-room-id/storage/json-patch" \
-H "Authorization: Bearer {{SECRET_KEY}}" \
-H "Content-Type: application/json" \
-d '[
{ "op": "test", "path": "/count", "value": 10 },
{ "op": "replace", "path": "/count", "value": 11 },
{ "op": "add", "path": "/log/-", "value": "updated" }
]'
```

For the full request/response schema and more examples, see the
[API reference](/docs/api-reference/rest-api-endpoints) for the Storage JSON
Patch endpoint.
Loading