How the Plane Live Application Handles Real-Time Collaboration: Yjs CRDT Architecture
Plane implements real-time collaborative editing by combining the Yjs conflict-free replicated data type (CRDT) library with the Hocuspocus WebSocket provider, enabling conflict-free document merging and resilient offline-first synchronization.
Plane's open-source codebase (makeplane/plane) delivers live collaboration through a split architecture: a React client layer manages document state and persistence, while a Node.js service coordinates multi-user synchronization. This implementation allows multiple users to edit project documents simultaneously without locks or version conflicts.
Architecture Overview: Yjs and Hocuspocus
The Plane live application builds on two core technologies. Yjs provides the CRDT implementation that represents document state as operations capable of merging automatically, while Hocuspocus serves as the WebSocket provider that transports these operations between clients and the server.
This combination ensures that every keystroke propagates as a discrete operation that converges identically across all participants, regardless of network latency or concurrent edits. The system separates concerns between document structure (Yjs), network transport (Hocuspocus), and UI state management (React hooks).
Client-Side Document Lifecycle
All client-side collaboration logic converges in the use-yjs-setup.ts hook located at packages/editor/src/core/hooks/use-yjs-setup.ts. This custom React hook encapsulates the entire Yjs document lifecycle, from instantiation to destruction.
Initializing the Yjs Document and Provider
Within the hook's initialization phase, the system creates a new Y.Doc instance to hold the shared document state. It immediately instantiates a HocuspocusProvider, passing the document identifier, WebSocket endpoint URL, and authentication token.
The provider establishes the WebSocket connection to the server and begins synchronizing the local document state with the global truth. The hook returns an object containing the provider instance, the ydoc reference, the current state, and control actions for the consuming component.
State Management and Connection Events
The hook tracks collaboration state through a CollaborationState object defined in packages/editor/src/types/collaboration.ts. It listens to provider callbacks including onSynced, onStatus, and onClose to transition between stages: awaiting-sync, synced, reconnecting, and disconnected.
Specifically, lines 73-99, 120-144, and 170-190 of use-yjs-setup.ts contain the state transition logic. When the provider emits a sync event, the state shifts to synced, enabling the editor. Status changes trigger UI updates to indicate connectivity health, while close events initiate recovery logic.
Offline Persistence with IndexedDB
To support offline-first editing, the hook configures IndexeddbPersistence within a secondary useEffect block (lines 70-85 of use-yjs-setup.ts). This persistence layer mirrors the Yjs document into the browser's IndexedDB, ensuring that the last known state remains available even when the server is unreachable.
When the editor initializes offline, it hydrates from this local cache first, then synchronizes with the server upon reconnection. This approach guarantees that users retain editing capabilities during network interruptions.
Server-Side WebSocket Coordination
The server implementation resides in packages/services/src/live.service.ts, which instantiates the Hocuspocus server endpoint. This service validates incoming authentication tokens before accepting WebSocket upgrades, ensuring that only authorized users join specific document sessions.
Once authenticated, the server maintains the HocuspocusProvider instance and manages the Yjs document's connection to the backend storage. It broadcasts incoming CRDT operations to all connected peers sharing the same document identifier, while persisting document updates to the database for durability.
Resilience and Reconnection Strategy
The collaboration layer distinguishes between deliberate disconnections and transient network failures. The handleClose function in use-yjs-setup.ts detects forced closures—such as when a user manually disconnects—and marks the connection with a forced-close flag that pauses automatic reconnection.
For transient losses, the system implements a limited retry loop attempting three reconnections before settling into an offline state. The handleVisibilityChange and handleOnline functions monitor page visibility and browser online events, automatically re-enabling the provider when the user returns to the tab or the network recovers.
Integration Example: Consuming the Hook
Components integrate collaboration capabilities by importing the useYjsSetup hook and passing configuration parameters including docId, serverUrl, authToken, and an optional onStateChange callback. The hook returns null until initialization completes, signaling the UI to render loading states.
import { useYjsSetup } from '@/packages/editor/src/core/hooks/use-yjs-setup';
export const Editor = ({ docId, token }) => {
const yjs = useYjsSetup({
docId,
serverUrl: 'wss://live.plane.so',
authToken: token,
onStateChange: (state) => {
console.log('Collab state:', state);
},
});
if (!yjs) return <div>Loading editor…</div>;
const { provider, ydoc, state } = yjs;
return (
<RichTextEditor
ydoc={ydoc}
provider={provider}
disabled={state.isServerDisconnected}
/>
);
};
The RichTextEditor component, located at packages/editor/src/components/RichTextEditor.tsx, consumes the ydoc and provider props to bind a Y-Prosemirror or TipTap instance to the collaborative session.
Summary
- Plane uses Yjs CRDTs to represent collaborative documents as conflict-free operation sets that merge automatically across clients.
- The
use-yjs-setup.tshook manages the entire client lifecycle, including WebSocket connection, IndexedDB persistence, and state transitions. - Server coordination occurs through
live.service.ts, which authenticates tokens and broadcasts updates via the Hocuspocus WebSocket provider. - Offline resilience is achieved through IndexedDB caching and automatic reconnection logic that handles transient failures separately from forced disconnections.
- State transparency is maintained through the
CollaborationStateinterface, enabling UI components to react to sync status, connectivity changes, and errors.
Frequently Asked Questions
How does Plane resolve conflicts when multiple users edit simultaneously?
Plane relies on the Yjs CRDT algorithm, which assigns globally unique identifiers to every document operation. When concurrent edits occur, Yjs merges the operations mathematically without requiring a central locking mechanism or last-write-wins heuristics. According to the implementation in use-yjs-setup.ts, these merged operations propagate through the HocuspocusProvider to all connected clients, ensuring eventual consistency.
What happens to edits when the network connection drops?
The client maintains an IndexeddbPersistence layer that continuously snapshots the Yjs document to the browser's IndexedDB. As implemented in the second useEffect block of use-yjs-setup.ts (lines 70-85), this cache allows the editor to load and accept changes offline. When connectivity returns, the hook automatically re-establishes the WebSocket connection and synchronizes the pending local operations with the server state.
How does the server authenticate WebSocket connections?
The live.service.ts file validates authentication tokens during the WebSocket handshake phase before establishing the HocuspocusProvider connection. The service checks token validity against the Plane user system, ensuring that only users with proper permissions can subscribe to specific document update streams. Invalid tokens result in an immediate connection termination before any document data transmits.
Can users deliberately disconnect from the live collaboration session?
Yes, the handleClose function within use-yjs-setup.ts differentiates between accidental disconnections and deliberate closures. When a forced close event occurs, the hook sets an internal flag that suppresses the automatic reconnection logic, keeping the session offline until the user explicitly reconnects. This prevents unnecessary network activity and IndexedDB synchronization attempts when a user intentionally exits the collaborative editor.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →