# How the Plane Live Application Handles Real-Time Collaboration: Yjs CRDT Architecture

> Discover how Plane uses Yjs CRDT architecture for seamless real-time collaboration. Experience conflict-free merging and offline-first sync.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: architecture
- Published: 2026-08-22

---

**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`](https://github.com/makeplane/plane/blob/main/use-yjs-setup.ts) hook located at [`packages/editor/src/core/hooks/use-yjs-setup.ts`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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.

```tsx
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`](https://github.com/makeplane/plane/blob/main/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.ts`](https://github.com/makeplane/plane/blob/main/use-yjs-setup.ts) hook** manages the entire client lifecycle, including WebSocket connection, IndexedDB persistence, and state transitions.
- **Server coordination** occurs through [`live.service.ts`](https://github.com/makeplane/plane/blob/main/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 `CollaborationState` interface, 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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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`](https://github.com/makeplane/plane/blob/main/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.