# How Plane Implements Real-Time Collaborative Document Editing Using Tiptap and Hocuspocus

> Learn how Plane achieves real-time collaborative document editing with Tiptap and Hocuspocus. Discover its conflict-free concurrent editing and automatic reconnection features.

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

---

**Plane implements real-time collaborative document editing by combining Tiptap's ProseMirror-based editor with Yjs CRDTs and Hocuspocus WebSocket providers, enabling conflict-free concurrent editing across clients with automatic reconnection and server-side persistence.**

The open-source project management platform **makeplane/plane** delivers seamless collaborative editing through a tightly integrated stack of three core technologies. This architecture allows multiple users to simultaneously edit page content and titles without locking mechanisms, while maintaining data consistency through conflict-free replicated data types (CRDTs) and providing resilience against network interruptions.

## Architecture Overview

Plane's collaborative editing stack rests on three pillars that handle distinct responsibilities:

- **Tiptap** provides the ProseMirror-based rich-text editing interface on the client.
- **Yjs** acts as the CRDT engine that holds the document model and merges concurrent changes.
- **Hocuspocus** supplies both the client-side WebSocket provider (`@hocuspocus/provider`) and the server-side WebSocket router (`@hocuspocus/server`) that synchronizes Yjs updates between clients and persists them to storage.

This separation ensures that the user interface remains decoupled from synchronization logic, while the server focuses solely on routing updates and managing persistence extensions.

## Client-Side Initialization

### Setting Up the HocuspocusProvider

The entry point for real-time collaboration begins 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 hook initializes the WebSocket connection and manages the document lifecycle.

At lines 62-66, the hook creates a `HocuspocusProvider` instance with the document ID, authentication token, and server URL:

```typescript
// packages/editor/src/core/hooks/use-yjs-setup.ts (lines 62-66)
const provider = new HocuspocusProvider({
  url: serverUrl,
  name: docId,
  token: authToken,
  // ... additional configuration
});

```

The provider installs callbacks for connection lifecycle events including `onConnect`, `onStatus`, `onSynced`, and `onClose`. When the client receives the `synced` event (lines 100-104), the hook updates its internal state to reflect that the document is ready for editing.

To support offline-first behavior, the hook also initializes `IndexeddbPersistence` at lines 77-81 via the `onIdbSynced` callback. This caches the Yjs document locally in the browser's IndexedDB, allowing immediate rendering of cached content while the network synchronization proceeds in the background.

### Configuring Tiptap with the Collaboration Extension

Once the provider is established, the [`use-collaborative-editor.ts`](https://github.com/makeplane/plane/blob/main/use-collaborative-editor.ts) hook (located at [`packages/editor/src/core/hooks/use-collaborative-editor.ts`](https://github.com/makeplane/plane/blob/main/packages/editor/src/core/hooks/use-collaborative-editor.ts)) wires Tiptap to the shared Yjs document.

The hook constructs the editor's extension array, inserting the `Collaboration` extension twice: once for the main document body and once for the page title. At lines 80-84, the configuration binds the main content to the `default` field of the Yjs document:

```typescript
// packages/editor/src/core/hooks/use-collaborative-editor.ts (lines 80-84)
Collaboration.configure({
  document: provider.document,
  field: "default",  // Main content XmlFragment
}),

```

For the title field, lines 66-70 configure a separate collaboration instance:

```typescript
// packages/editor/src/core/hooks/use-collaborative-editor.ts (lines 66-70)
Collaboration.configure({
  document: provider.document,
  field: "title",  // Title XmlFragment
}),

```

This dual-field approach allows concurrent editing of both the document title and body as separate entities within the same shared `Y.Doc`.

## Server-Side Infrastructure

### The Hocuspocus Server Singleton

On the server side, Plane uses a singleton pattern to manage the Hocuspocus instance. The [`apps/live/src/hocuspocus.ts`](https://github.com/makeplane/plane/blob/main/apps/live/src/hocuspocus.ts) file declares the `HocusPocusServerManager` class, which initializes the WebSocket server at lines 45-53:

```typescript
// apps/live/src/hocuspocus.ts (lines 45-53)
const hocuspocus = new Hocuspocus({
  port: parseInt(process.env.PORT || "3000"),
  address: process.env.HOST || "0.0.0.0",
  onAuthenticate,
  onStateless,
  ...getExtensions(),
});

```

This singleton ensures that all WebSocket connections route through a single server instance, optimizing memory usage and maintaining consistent state across the application.

### Authentication and Persistence Extensions

Security and durability are handled through Hocuspocus extensions. At lines 47-48, the server registers an `onAuthenticate` callback that validates the JWT token provided by the client during the initial handshake.

For persistence, the server loads extensions at lines 49-50 that include `@hocuspocus/extension-redis` for pub/sub capabilities and `@hocuspocus/extension-database` for long-term storage of the Yjs document state. These extensions ensure that document updates survive server restarts and remain available to late-joining collaborators.

## The Synchronization Flow

When a user types in the Tiptap editor, the following chain executes:

1. **Local Mutation**: Tiptap's `Collaboration` extension captures the ProseMirror transaction and translates it into Yjs operations on the shared `Y.Doc`.
2. **CRDT Merge**: Yjs automatically merges these operations with any concurrent changes from other users, eliminating conflicts without requiring locks.
3. **WebSocket Transmission**: The `HocuspocusProvider` forwards the Yjs update message through the open WebSocket connection to the server.
4. **Server Broadcast**: The Hocuspocus server receives the update and broadcasts it to all other connected clients subscribed to the same document ID.
5. **Remote Application**: Other clients receive the update, apply it to their local `Y.Doc` instances, and Tiptap's ProseMirror view re-renders only the changed nodes, creating a near-real-time collaborative experience.

## Resilience and Offline-First Capabilities

Plane's implementation handles network instability through sophisticated reconnection logic in [`use-yjs-setup.ts`](https://github.com/makeplane/plane/blob/main/use-yjs-setup.ts). The hook monitors `onClose` events at lines 41-70 to distinguish between forced disconnections (error codes 4000-4003, typically authentication failures) and transient network losses.

For transient failures, the hook implements automatic reconnection with exponential backoff up to a configurable `DEFAULT_MAX_RETRIES` limit. It also listens for page visibility and online/offline browser events (lines 71-92) to intelligently pause synchronization when the user switches tabs or loses connectivity, then resume seamlessly when the network returns.

The IndexedDB persistence layer ensures that users can continue editing during complete offline periods, with changes syncing automatically once the connection restores.

## Summary

- **Three-tier architecture**: Tiptap handles the UI, Yjs manages the CRDT document model, and Hocuspocus provides WebSocket transport and server-side persistence.
- **Dual-field collaboration**: Plane separates the document title and body into distinct Yjs fields (`"title"` and `"default"`) within a single shared document, configured in [`use-collaborative-editor.ts`](https://github.com/makeplane/plane/blob/main/use-collaborative-editor.ts).
- **Offline-first design**: IndexedDB caching in [`use-yjs-setup.ts`](https://github.com/makeplane/plane/blob/main/use-yjs-setup.ts) allows immediate content rendering and continued editing during network interruptions.
- **Automatic resilience**: The client implements intelligent reconnection logic that distinguishes fatal errors from transient network issues, with configurable retry limits.
- **Server extensibility**: The Hocuspocus server in [`apps/live/src/hocuspocus.ts`](https://github.com/makeplane/plane/blob/main/apps/live/src/hocuspocus.ts) supports authentication hooks and persistence extensions for Redis and database storage.

## Frequently Asked Questions

### How does Plane handle simultaneous edits from multiple users?

Plane uses **Yjs CRDTs** (Conflict-free Replicated Data Types) to merge concurrent edits automatically. When multiple users type simultaneously, Yjs applies their respective operations to the shared document without requiring locks or causing version conflicts. Tiptap's `Collaboration` extension translates these merged changes into ProseMirror transactions, ensuring all clients converge to the same document state regardless of network latency.

### What happens when a user loses internet connection while editing?

The client remains functional through the **IndexedDB persistence** layer initialized in [`use-yjs-setup.ts`](https://github.com/makeplane/plane/blob/main/use-yjs-setup.ts). Local edits continue to apply to the cached Yjs document stored in the browser. When connectivity returns, the `HocuspocusProvider` automatically synchronizes the pending changes with the server. The hook also monitors browser online/offline events to pause sync attempts during disconnection and resume them upon reconnection.

### How does the server persist collaborative documents?

The Hocuspocus server in [`apps/live/src/hocuspocus.ts`](https://github.com/makeplane/plane/blob/main/apps/live/src/hocuspocus.ts) loads persistence extensions including `@hocuspocus/extension-database` and `@hocuspocus/extension-redis`. These extensions store the authoritative Yjs document state to the database and Redis cache respectively, ensuring that document changes survive server restarts and remain available for users who join the collaborative session later.

### Can the collaborative editing features work across different document types?

Yes, the architecture is document-agnostic. The [`use-yjs-setup.ts`](https://github.com/makeplane/plane/blob/main/use-yjs-setup.ts) hook accepts any `docId` parameter to initialize the provider, and the [`use-collaborative-editor.ts`](https://github.com/makeplane/plane/blob/main/use-collaborative-editor.ts) hook configures the Tiptap editor with the specific extensions needed for that document type. As long as the server recognizes the document ID and the client has the appropriate Tiptap extensions loaded, the same Yjs and Hocuspocus infrastructure supports any collaborative editing scenario within Plane.