AI agents: fetch the documentation index at llms.txt. Markdown versions are available by appending .md to any page URL, including this page's markdown.
Collaborative editing with Yjs
Set up real-time, multi-user collaborative editing using CRDTs, presence tracking, and rich-text editor integration.
Regular fields use server-sequenced LWW sync. Collaborative fields use Yjs CRDTs for character-level merge: no central server needed for conflict resolution.
Architecture overview
Each client binds to a local Yjs document that syncs through the shared WebSocket.
flowchart TB subgraph Client A EA[Editor] --> YA[Y.Doc] YA --> WA[WebSocket] end subgraph Client B EB[Editor] --> YB[Y.Doc] YB --> WB[WebSocket] end subgraph Server WA --> SS[Sync Server] WB --> SS SS --> Redis[(Redis)] SS --> PubSub[PubSub] PubSub --> SS SS --> DB[(Database)] end
Demand-driven sessions: Live editing starts only when 2+ users view a document and one is editing, cutting server usage by ~96%.
Shared WebSocket: Yjs updates piggyback on the existing sync connection.
Standard sync fallback: If live editing goes down, no data is lost.
Setting up YjsDocumentManager
YjsDocumentManager manages Yjs document instances and their server connection.
import { YjsDocumentManager } from "@stratasync/y-doc";// The transport adapter provides the WebSocket connectionconst yjsTransport = transport.getYjsTransport();const documentManager = new YjsDocumentManager({ transport: yjsTransport,});
Getting a document
Get a Y.Doc for a model instance. Documents are created, cached, and connected automatically.
import type { DocumentKey } from "@stratasync/y-doc";const docKey: DocumentKey = { entityType: "Task", entityId: taskId, fieldName: "description",};// Get or create a Y.Doc for a specific task's descriptionconst doc = documentManager.getDocument(docKey);// Access the shared text typeconst yText = doc.getText("content");
"content" is the shared Y.Text type name. Multiple clients with the same document ID share state.
Connection management
// Connect to start receiving/sending updatesdocumentManager.connect(docKey);// Check connection stateconst state = documentManager.getConnectionState(docKey);// "disconnected" | "connecting" | "syncing" | "connected"// Disconnect when donedocumentManager.disconnect(docKey);
Using the useYjsDocument hook
useYjsDocument provides a reactive interface to Yjs documents in React.
"use client";import { useYjsDocument } from "@stratasync/react";export function CollaborativeEditor({ taskId }: { taskId: string }) { const { doc, connectionState, content } = useYjsDocument({ entityType: "Task", entityId: taskId, fieldName: "description", }); if (connectionState === "connecting") { return <p>Connecting to editing session...</p>; } if (!doc) { return <p>Loading document...</p>; } // `content` is a reactive string derived from doc.getText("content") // `doc` is the raw Y.Doc for editor binding return ( <div> <p>Status: {connectionState}</p> <Editor doc={doc} /> </div> );}
const { startViewing, stopViewing, focus, blur, isViewing, isEditing } = useYjsPresence({ entityType: "Task", entityId: taskId, fieldName: "description", });// Start viewing when you navigate to the documentstartViewing();// Signal editing when you focus an inputfocus(); // Automatically calls startViewing() if not already viewing// Signal blur when you leave the inputblur();// Stop viewing when you navigate awaystopViewing();
Use trackFocus and trackVisibility to handle these automatically via a ref callback.
Session lifecycle
Sessions start on demand and tear down after a grace period.
stateDiagram-v2 [*] --> NoSession: Document opened NoSession --> LiveSession: >= 2 viewers AND >= 1 editor LiveSession --> GracePeriod: < 2 viewers OR 0 editors GracePeriod --> NoSession: Grace period expires (15-30s) GracePeriod --> LiveSession: Conditions met again LiveSession --> [*]: All users leave
Without a live session, clients operate independently via standard sync. Edits are CRDT deltas, so all clients merge seamlessly when a session starts.
Integration with rich-text editors
Tiptap
Tiptap has first-class Yjs support. Disable its built-in history since Yjs handles undo/redo.
Get yText and awareness from the Yjs document and presence manager. See @stratasync/y-doc for details.
Offline collaboration
Edit while offline: changes apply to the local Y.Doc and buffer until connectivity returns. CRDT deltas sync to the server, and concurrent edits merge automatically at the character level.