Architecture of the Hocuspocus Collaborative Server in Plane: A Technical Deep Dive

The Hocuspocus collaborative server in Plane employs a singleton manager pattern that maintains a single server instance per process, wired into an Express/WebSocket stack and extended via a modular pipeline handling persistence, horizontal scaling, and real-time synchronization.

Plane leverages the open-source Hocuspocus engine to power its real-time collaborative editing features. The architecture centers on a HocusPocusServerManager that orchestrates document synchronization through a pluggable extension system, seamlessly bridging the collaborative editing protocol with Plane's existing authentication and persistence layers.

Core Architectural Components

The server-side implementation is organized around three primary pillars: the singleton manager, the HTTP/WebSocket transport layer, and the extensions pipeline.

HocusPocusServerManager Singleton

At the heart of the architecture sits the HocusPocusServerManager, implemented in apps/live/src/hocuspocus.ts. This class guarantees that only one Hocuspocus instance exists per process, encapsulating its configuration and lifecycle management. The manager exposes a getInstance() method that returns the singleton, ensuring consistent state across the application.

The singleton is configured with a unique name (derived from the hostname or a UUID), authentication hooks, and a debounced write-back interval of 10 seconds to optimize database persistence.

Express Live Server Integration

The transport layer resides in apps/live/src/server.ts, where an Express application hosts the HTTP API and handles WebSocket upgrades. The server initializes middleware for security (Helmet), compression, CORS, and logging before mounting the live router at env.LIVE_BASE_PATH.

Using expressWs(this.app), the server upgrades HTTP connections to WebSocket protocol, allowing Hocuspocus to listen on those sockets and coordinate collaborative editing sessions.

Extensions Pipeline

Extensions are aggregated in apps/live/src/extensions/index.ts and supplied to the Hocuspocus constructor via a getExtensions() function. This modular design allows Plane to inject custom behaviors without modifying core Hocuspocus logic. The standard extension set includes:

  • Logger: Emits server-side logs for document lifecycle events
  • Database: Implements fetch and store callbacks for persistence
  • Redis: Enables cross-instance broadcasting via pub/sub channels
  • TitleSyncExtension: Synchronizes document titles between collaborators
  • ForceCloseHandler: Gracefully disconnects clients when documents exceed size limits

Server Startup and Initialization Flow

The initialization sequence follows a strict five-step process to ensure all dependencies are ready before accepting connections:

  1. Express Application Creation – The Server class constructs an Express app, registers global middleware, and mounts the live router at the configured base path.

  2. Redis Initialization – redisManager.initialize() establishes connections to the Redis cluster, enabling pub/sub channels used by extensions for horizontal scaling.

  3. Hocuspocus Instantiation – HocusPocusServerManager.getInstance().initialize() creates a new Hocuspocus instance with:

    • onAuthenticate: Validates incoming JWTs via @/lib/auth
    • onStateless: Handles context-free messages (e.g., pings) via @/lib/stateless
    • extensions: The collection from getExtensions()
    • debounce: 10-second delay for batching document write-backs
  4. Controller Registration – Plane controllers are registered using registerController(this.router, controller, [hocuspocusServer]), allowing business logic to subscribe to document events.

  5. WebSocket Upgrade – The Express server begins listening, with expressWs handling the protocol upgrade for Hocuspocus connections.

Extension System and Custom Behaviors

Extensions implement the Extension interface from @hocuspocus/server, hooking into document lifecycle events to provide Plane-specific functionality.

Database Extension and Persistence

The Database extension in apps/live/src/extensions/database.ts bridges Hocuspocus with Plane's page service. It implements two critical callbacks:

  • fetch: Retrieves the binary HTML representation of a page from Plane's database and converts it to the format required by the editor
  • store: Persists updated document states back to the database, handling binary serialization

This extension ensures that collaborative edits are durably stored while maintaining compatibility with Plane's existing page format.

Redis Integration for Horizontal Scaling

The Redis extension utilizes the redisManager imported in server.ts to broadcast state changes across multiple server pods. This enables scenarios where users connected to different instances of the Plane live server can collaborate on the same document in real-time. The pub/sub channels specifically handle force-close commands and title synchronization events.

Lifecycle and Sync Extensions

The TitleSyncExtension monitors document metadata changes and propagates title updates to all connected clients. The ForceCloseHandler listens for critical error conditions (such as document size violations) and initiates graceful disconnection sequences to prevent data corruption.

Client-Server Interaction Model

Clients (such as the Plane web editor using @hocuspocus/client) establish WebSocket connections to <HOST>/live. After negotiating the document ID and validating the JWT token through the onAuthenticate hook, the server streams CRDT (Conflict-free Replicated Data Type) operations to connected peers.

import { HocuspocusProvider } from "@hocuspocus/provider";

const provider = new HocuspocusProvider({
  url: `${window.location.origin}/live`,
  name: "my-document-id",
  token: USER_JWT,
});

provider.on("synced", () => console.log("Document ready"));
provider.on("update", ({ added, removed }) => {
  // Handle CRDT updates
});

The server processes these operations through the extensions pipeline, persisting changes via the Database extension and broadcasting to other instances through Redis.

Implementation Example: Creating Custom Extensions

Developers can extend the system by implementing the Extension interface and registering the extension in apps/live/src/extensions/index.ts:

import { Extension } from "@hocuspocus/server";

class AuditLogger extends Extension {
  async onConnect({ documentName, instance }) {
    console.log(`[Audit] ${instance.id} connected to ${documentName}`);
  }
}

export const getExtensions = () => [
  new Logger(),
  new Database(),
  new Redis(),
  new TitleSyncExtension(),
  new ForceCloseHandler(),
  new AuditLogger(),
];

Summary

  • Singleton Pattern: The HocusPocusServerManager in apps/live/src/hocuspocus.ts ensures a single server instance per process, preventing resource conflicts.
  • Modular Extensions: The system uses a pipeline of extensions for persistence (Database), scaling (Redis), and synchronization (TitleSyncExtension, ForceCloseHandler).
  • Integration Points: The server integrates with Plane's Express stack via apps/live/src/server.ts, utilizing standard middleware and WebSocket upgrades.
  • Persistence Strategy: A 10-second debounce optimizes database writes, with the Database extension handling conversion between Plane's binary format and Hocuspocus requirements.
  • Horizontal Scaling: Redis pub/sub enables multi-instance deployments, allowing users on different server pods to collaborate seamlessly.

Frequently Asked Questions

What is the HocusPocusServerManager and why is it implemented as a singleton?

The HocusPocusServerManager is a wrapper class defined in apps/live/src/hocuspocus.ts that guarantees only one Hocuspocus instance exists per Node.js process. This singleton pattern prevents race conditions and resource leaks when multiple parts of the application need access to the collaborative editing engine, ensuring consistent document state and centralized configuration management.

How does the Database extension handle document persistence in Plane?

The Database extension in apps/live/src/extensions/database.ts implements the fetch and store callbacks required by Hocuspocus. When a document is requested, fetch retrieves the binary HTML from Plane's page service and converts it to the editor's required format. The store callback persists updates back to the database with a 10-second debounce to batch writes and reduce database load.

Can the Hocuspocus server scale horizontally in Plane?

Yes, the architecture supports horizontal scaling through the Redis extension and redisManager. When deployed across multiple pods or instances, Redis pub/sub channels broadcast state changes (such as title updates and force-close commands) between servers. This ensures that users connected to different instances can collaborate on the same document in real-time without state desynchronization.

How is authentication handled in the collaborative editing connections?

Authentication is implemented through the onAuthenticate hook configured in the HocusPocusServerManager. This hook validates JWT tokens passed by clients (via the token parameter in HocuspocusProvider) using the authentication logic in @/lib/auth. Only validated connections gain access to document editing capabilities, ensuring that collaborative sessions respect Plane's existing permission models.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →