Storage
Storage is the persistent document inside Sync. Multiple humans and agents can edit it at the same time, and conflict resolution merges simultaneous changes without losing unrelated edits. Documents remain after every user disconnects, unlike Presence, which is temporary.
Conflict-free data types
Storage documents are built from conflict-free data types, each resolving simultaneous edits differently.
| Data type | Description | Example value |
|---|---|---|
LiveObject | A record of named values, similar to a JavaScript object. | LiveObject({ x: 50, y: 100, color: "red" }) |
LiveList | An ordered collection, similar to a JavaScript array. | LiveList(["rectangle-1", "circle-2"]) |
LiveMap | A key-value collection, similar to a JavaScript map. | LiveMap([["pierre", 152], ["alicia", 186]]) |
LiveText | A collection of text nodes, storing formatting data. | LiveText(["Hello world", { format: "bold" }]) |
LiveFile | An immutable reference to an uploaded file. | LiveFile({ name: "photo.png", size: 984, ... }) |
LiveObject
LiveObject is similar to a
JavaScript object that is synchronized on all clients—users can update different
properties at the same time, and changes are merged together. Use it for storing
records with fixed key names and where the values don’t necessarily have the
same types, for example, a shape on a canvas.
LiveList
LiveList is similar to a
JavaScript array that is synchronized across clients—users can delete, insert,
and move items at the same time as others, and changes are merged together. Use
it for storing an ordered collection of items, for example a list of layers on a
canvas.
LiveMap
LiveMap is similar to a
JavaScript map that is synchronized across clients—users can update, delete, and
insert items at the same time as others, and changes are merged together
automatically. Use it for storing key-value pairs, for example a list of users
and their upvotes.
LiveText
LiveText is used to store
collaborative rich text data—users can insert, replace, and format text at the
same time as others, and changes are merged together automatically. It’s
generally most useful when used internally by a
text editing integration, but it can be
handled directly too.
LiveText supports
Tiptap,
BlockNote,
ProseMirror, and
CodeMirror.
LiveFile
LiveFile is used to store a
reference to a file uploaded to Liveblocks. Any kind of file can be uploaded
with useUploadFile and
attached to your data tree.
Nesting data types
LiveObject, LiveList, and LiveMap can each contain other live structures,
allowing you to build a full JSON-like document tree. Each part of the structure
can be edited independently, and changes are merged together automatically. For
example, a LiveList of shapes may contain LiveObject shapes.
In the following complex example, all data types are used, and some modifications are made.
Using Storage
Setting up
Before using Storage, first decide on the shape of your document, and set the
Storage type in your Liveblocks config file. This makes every hook and
mutation in your app fully typed, with TypeScript. In this example, this canvas
document holds a list of shapes.
Next, in React, create an initial document with
initialStorage
on RoomProvider. It’s
applied once, the first time a room is entered—after that, the stored document
is the source of truth.
Now that the type has been set, and the initial document has been created, you can now start to use Storage in your application.
Reading data
useStorage reads a part of
the document, returning it as an immutable JSON value. For example, a LiveList
is converted into a plain JavaScript array, making it easy to render it in your
component. Using a selector function like (root) => root.shapes, you can fetch
only the part of the document you need, and it will re-render in realtime as
other users make changes.
Updating data
useMutation creates a
callback that modifies the document. Changes apply instantly for the current
user, sync to everyone else in realtime, and merge with any simultaneous edits.
Mutations can accept extra arguments after the context object, so you can pass
in values when calling them. The following mutation takes a color property,
and sets it on a shape at a given index.
Storage can also be modified from your back end and by AI agents, learn more under server-side editing and agentic editing.
Creating a multiplayer component
Putting together useStorage
and useMutation, you can
create editable multiplayer components in your app. For example, a select menu
that updates in realtime for all users.
Undo and redo changes
When using Storage, each user has an independent undo stack which is saved in memory until the user leaves the page. Using undo reverts the user’s Storage changes without reversing work made by other collaborators.
The useUndo and
useRedo hooks can be used to
trigger undo/redo actions. Additionally
useCanUndo and
useCanRedo can be used to
check if there are any actions to undo/redo, helpful for disabling buttons. Put
these together to create undo and redo buttons.
Pause and resume history
You can choose to pause undo/redo history when a single gesture should count as a single undo step. For example, picture dragging a shape across a canvas—it moves 200 pixels, but you wouldn’t want to press undo 200 times. Pausing history allows you to merge this drag action into one undo step. Intermediate positions still sync to other clients, but undo restores the position from before the drag.
useHistory also exposes
disable for when a local change should not enter the stack at all.
Presence history
Undo only affects Storage by default, but it can sometimes be helpful to revert
Presence changes too. For example, when a shape is dragged on a canvas, a user’s
Presence may be visible around the shape. If the shape is moved back with undo,
it makes sense to move the user’s selection back too. Pass
{ addToHistory: true } when updating Presence to add a value to the undo/redo
stack.
Note that useMutation
provides a setMyPresence helper, so that you don't need to import
useUpdateMyPresence
separately.
Version history
Storage supports version history, allowing you to snapshot document states and revert to previous versions of the document.
To learn more about how to do this, read the version history page.
Server-side editing
You can read and edit Storage directly from the server, using conflict-free data types or JSON Patch.
To learn more about how to do this, read the server-side editing page.
Agentic editing
AI agents can generate changes for your document, using Node.js methods or JSON Patch.
To learn more about how to do this, read the agentic editing page.