Yjs
Use crdtRoom() with Yjs when your client already produces Yjs updates. Send
binary updates as base64 strings so the backend can store them safely and keep
the hot room state optimized inside Durable Objects.
app.post("/documents/:documentId/yjs", async (request) => { const pathSegments = new URL(request.url).pathname.split("/") const documentId = pathSegments[2] const body = await request.json()
return await live.crdtRoom(documentId).applyYjsUpdate({ updateBase64: body.updateBase64, clientId: body.clientId, clock: body.clock, })})Connect over WebSocket
Section titled “Connect over WebSocket”Create a short-lived room grant on the backend and return it to the browser:
app.post("/documents/:documentId/socket", async (request) => { const pathSegments = new URL(request.url).pathname.split("/") const documentId = pathSegments[2] const body = await request.json()
return await live.crdtRoom(documentId).authorize({ clientId: body.clientId, presence: body.presence, })})Use the returned URL and protocols in the browser WebSocket constructor:
const grant = await fetch(`/documents/${documentId}/socket`, { method: "POST", body: JSON.stringify({ clientId, presence: { cursor: 12 }, }),}).then((response) => response.json())
const socket = new WebSocket(grant.url, grant.protocols)
socket.send(JSON.stringify({ type: "yjs.update", updateBase64,}))
socket.send(JSON.stringify({ type: "presence", state: { cursor: 18 },}))The grant is bound to the room, selected actor, client id, initial presence, and expiration time.
Sync from a state vector
Section titled “Sync from a state vector”Clients can send their current Yjs state vector and receive the missing update:
app.post("/documents/:documentId/sync", async (request) => { const pathSegments = new URL(request.url).pathname.split("/") const documentId = pathSegments[2] const body = await request.json()
return await live.crdtRoom(documentId).syncYjs({ stateVectorBase64: body.stateVectorBase64, limit: 200, })})The result includes a merged updateBase64, the current stateVectorBase64,
recent update records, and awareness records.
Awareness
Section titled “Awareness”Yjs document updates and awareness are separate. Store cursors, selections, and
online editor state with awareness():
await live.crdtRoom("document_123").awareness({ clientId: "client_1", state: { cursor: 12, selection: { from: 4, to: 12 }, },})Compact hot state
Section titled “Compact hot state”Use compact() to ask the room to summarize accumulated Yjs updates into a
checkpoint:
const checkpoint = await live.crdtRoom("document_123").compact()Realtime keeps recent update history in the Database product and keeps the hot merged update and state vector in the room Durable Object. This gives active documents fast sync while preserving product-level history.
Yjs room WebSockets use Durable Object hibernation. When a document has no active traffic, Cloudflare can hibernate the Durable Object while keeping clients connected. On wakeup, Realtime restores the socket attachments and the room can continue broadcasting Yjs updates without requiring every client to rejoin first.