- Real-time editing over your own transport. Carets, presence, tracked changes, and comments are collaborative by default. Your authentication and infrastructure carry the traffic.
- Word round trip after concurrent edits. Edits merge as CRDT operations,
and the result exports to
.docxwith native tracked changes and comments. - A server in the same room.
reviseruns the same converters and document tools in Node. Your backend can create documents before anyone opens them, edit live rooms without a browser, and propose tracked changes for users to review.
One copy of Yjs
Yjs identifies its own types withinstanceof, so your application and the
SDK must resolve to the same Yjs module. Yjs is a peer dependency. Install
it yourself:
Unexpected content type in insert operation. In Vite, keep the
SDK and Yjs on the same side of the dependency optimizer:
vite.config.ts
yjs. Run npm ls yjs to check.
Connect callback
The editor reserves eight top-level keys in the Yjs document (content,
chrome, notes, comments, annotations, metadata, sdt-wrappers,
paragraphs) and needs specific document settings. The recommended form
gives you a document and an awareness instance to connect:
Supplying a Y.Doc
Some transports create the document themselves. Both of these forms work:
A refused document reports the conflicting key:
comments or metadata. Give the
editor its own document, or keep your data in a separate one.
Use the explicit form for an unrecognized transport, a custom sync layer, or a
setup where the document and the connection are separate objects:
Seeding
The first participant writes the content. Everyone else joins it.seed
controls this:
Seeding happens after the first sync. Seeding before receiving the room’s
state would duplicate the document.
Sync state
Setsynced to true once the provider has received the room’s initial state.
Until then, the editor keeps the document closed instead of showing an empty,
editable page:
synced. The editor opens
immediately, and a brand-new room can be seeded twice.
Roster
collaboration.getState() returns awareness as a list of peers, with the same
colors the carets use:
currentUser does not appear.
Presence and cursors
Remote carets render when awareness is available, with a label and color per participant. Peers see what you pass ascurrentUser:
name labels the caret. image is drawn on its flag and can be any URL the
browser can load, including a data: URI.
color is optional. Without it, the editor derives a stable color from the
user id, so each person has the same color for everyone. Set it to use your
own palette or to match an avatar color.
A participant with no currentUser has no caret for others to see. Pass one
for anyone who should be visible.
currentUser also sets the author of tracked changes. Without it,
suggestions export to Word with the reviewer name “Anonymous”.Server editing
Withrevise, your server is a peer in the room, not only a
relay. It runs the same converters and mutation engine as the editor, at the
same version. Changes your server makes to the Y.Doc reach every open browser
as ordinary updates, and browser changes reach your server the same way. This
section covers common workflows; the library reference
covers the full API.
Seeding from your own server
Your server can create the room from a file it already has:seed: "never" and no source file, and join the existing
document. This avoids a race where two people open a new document at the same
time and both import it. For a sync server built this way, see the
revise collaboration guide.
Editing a live room
To change a room on the server, open it withReviseDocument.fromYDoc. The
document has the same tools as the browser: read, search, measure, mutate, and
comment. Edits go through the same Yjs-backed mutation engine, so peers
receive ordinary updates and remote carets stay in place:
mode: "editing" or a
per-call { directMode: true }. Nothing an unattended backend job proposes is
applied until a person accepts it.
Review across server and browser
Server and browser share the same tool contract:doc.tools.call() on the
server and editor.tools.execute() in the browser return the same
ReviseToolResult, and mutations report the same suggestionIds. The server
can propose changes and a person can resolve them in the browser:
await doc.changes(). Each entry includes its author, so
the job can settle its own agent’s changes with doc.accept({ ids }) or
doc.reject({ ids }) and leave human ones alone. See tracked
changes for the review UI.
Working without a live transport
A server can participate without a socket. For an API endpoint or a queue worker, the client posts its document as one encoded update. The server edits a copy and returns only the delta. The delta merges into the live document like any peer’s edit, even if the user kept typing in the meantime:suggestionIds for the same review flow as above.
What the SDK does not do
- It does not connect. No socket, retries, authentication, or reconnection backoff. Your provider handles all of it.
- It does not persist. Your server decides where the document is stored between sessions.
- It does not destroy your
Y.Doc. A document you pass in outlives the component. Unmounting only clears this client’s caret. - It does not enforce who may edit. Roles
limit what each embed can do, but each client sets its own role, and there
is no document locking. To enforce a role, check it on the server at the
transport. With
revise, that server process can read and edit the document it guards.