> ## Documentation Index
> Fetch the complete documentation index at: https://docs.superdoc.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Hocuspocus

[Hocuspocus](https://tiptap.dev/docs/hocuspocus) is TipTap's open-source Yjs WebSocket server. It's a mature, battle-tested option for self-hosted collaboration.

## Setup

### Server

```bash theme={null}
npm install @hocuspocus/server yjs
```

```typescript theme={null}
import { Server } from "@hocuspocus/server";
import * as Y from "yjs";

const server = Server.configure({
  port: 1234,

  async onLoadDocument(data) {
    // Load the stored Yjs update from your database.
    const state = await db.getDocument(data.documentName);
    if (state) {
      Y.applyUpdate(data.document, state);
    }
    return data.document;
  },

  async onStoreDocument(data) {
    // Store the Yjs document as a binary update.
    const state = Y.encodeStateAsUpdate(data.document);
    await db.saveDocument(data.documentName, Buffer.from(state));
  },

  async onAuthenticate(data) {
    // Validate token
    const user = await validateToken(data.token);
    if (!user) {
      throw new Error("Unauthorized");
    }
    return { user };
  },
});

server.listen();
```

### Client

Use the provider-agnostic API to connect SuperDoc:

```bash theme={null}
npm install @hocuspocus/provider yjs
```

```typescript theme={null}
import { HocuspocusProvider } from "@hocuspocus/provider";
import * as Y from "yjs";
import { SuperDoc } from "superdoc";

const ydoc = new Y.Doc();
const provider = new HocuspocusProvider({
  url: "ws://localhost:1234",
  name: "document-123",
  document: ydoc,
  token: "auth-token", // Optional
});

// Wait for sync before creating editor
provider.on("synced", () => {
  const superdoc = new SuperDoc({
    selector: "#editor",
    documentMode: "editing",
    user: {
      name: "John Smith",
      email: "john@example.com",
    },
    modules: {
      collaboration: { ydoc, provider },
    },
  });
});
```

SuperDoc JS always uses the same collaboration contract, regardless of provider: `modules.collaboration = { ydoc, provider }`.

## Seed from DOCX only for empty rooms

A DOCX file can seed a new collaboration room. After that, the Yjs document is
the source of truth.

Wait for Hocuspocus to sync before you decide whether the room is empty. If you
check before sync, the client can look empty while the server still has stored
Yjs state.

```typescript theme={null}
function hasSuperDocContent(ydoc: Y.Doc) {
  return (
    ydoc.getXmlFragment("supereditor").length > 0 ||
    ydoc.getMap("parts").size > 0 ||
    ydoc.getMap("meta").has("docx")
  );
}

provider.on("synced", () => {
  const shouldSeedFromDocx = !hasSuperDocContent(ydoc);

  new SuperDoc({
    selector: "#editor",
    documentMode: "editing",
    ...(shouldSeedFromDocx
      ? {
          document: {
            type: "docx",
            url: "/initial.docx",
            isNewFile: true,
          },
        }
      : {}),
    user: {
      name: "John Smith",
      email: "john@example.com",
    },
    modules: {
      collaboration: { ydoc, provider },
    },
  });
});
```

<Note>
  If your backend already knows whether a room has stored Yjs state, use that
  metadata. The important part is the order: sync first, inspect the Yjs document
  or room metadata, then decide whether to pass the DOCX seed.
</Note>

## React example

```tsx theme={null}
import { useEffect, useRef, useState } from "react";
import { HocuspocusProvider } from "@hocuspocus/provider";
import * as Y from "yjs";
import { SuperDoc } from "superdoc";
import "superdoc/style.css";

function hasSuperDocContent(ydoc: Y.Doc) {
  return (
    ydoc.getXmlFragment("supereditor").length > 0 ||
    ydoc.getMap("parts").size > 0 ||
    ydoc.getMap("meta").has("docx")
  );
}

export default function Editor() {
  const superdocRef = useRef<SuperDoc | null>(null);
  const [users, setUsers] = useState<any[]>([]);

  useEffect(() => {
    const ydoc = new Y.Doc();
    const provider = new HocuspocusProvider({
      url: "ws://localhost:1234",
      name: "my-document",
      document: ydoc,
    });

    provider.on("synced", () => {
      if (superdocRef.current) return;

      const shouldSeedFromDocx = !hasSuperDocContent(ydoc);
      superdocRef.current = new SuperDoc({
        selector: "#superdoc",
        documentMode: "editing",
        ...(shouldSeedFromDocx
          ? {
              document: {
                type: "docx",
                url: "/initial.docx",
                isNewFile: true,
              },
            }
          : {}),
        user: {
          name: `User ${Math.floor(Math.random() * 1000)}`,
          email: "user@example.com",
        },
        modules: {
          collaboration: { ydoc, provider },
        },
        onAwarenessUpdate: ({ states }) => {
          setUsers(states.filter((s) => s.user));
        },
      });
    });

    return () => {
      superdocRef.current?.destroy();
      provider.destroy();
    };
  }, []);

  return (
    <div>
      <div className="users">
        {users.map((u, i) => (
          <span key={i} style={{ background: u.user?.color }}>
            {u.user?.name}
          </span>
        ))}
      </div>
      <div id="superdoc" style={{ height: "100vh" }} />
    </div>
  );
}
```

## Server configuration

### Basic options

```typescript theme={null}
Server.configure({
  port: 1234,
  timeout: 30000, // Connection timeout
  debounce: 2000, // Debounce document saves
  maxDebounce: 10000, // Max wait before save
  quiet: false, // Disable logging
});
```

### Hooks

| Hook              | Purpose                    |
| ----------------- | -------------------------- |
| `onLoadDocument`  | Load document from storage |
| `onStoreDocument` | Save document to storage   |
| `onAuthenticate`  | Validate user tokens       |
| `onChange`        | React to document changes  |
| `onConnect`       | Handle new connections     |
| `onDisconnect`    | Handle disconnections      |

### Persistence example

```typescript theme={null}
import { Server } from "@hocuspocus/server";
import { Database } from "@hocuspocus/extension-database";

const server = Server.configure({
  extensions: [
    new Database({
      fetch: async ({ documentName }) => {
        const doc = await db.findOne({ name: documentName });
        return doc?.data || null;
      },
      store: async ({ documentName, state }) => {
        await db.upsert({ name: documentName }, { data: state });
      },
    }),
  ],
});
```

## Provider options

```typescript theme={null}
const provider = new HocuspocusProvider({
  url: "ws://localhost:1234",
  name: "document-id",
  document: ydoc,

  // Optional
  token: "auth-token",
  awareness: awareness, // Custom awareness instance
  connect: true, // Auto-connect on create
  preserveConnection: true, // Keep connection on destroy
  broadcast: true, // Broadcast changes to tabs
});
```

## Events

### Provider events

```typescript theme={null}
// Sync status
provider.on("synced", () => {
  console.log("Document synced");
});

// Connection status
provider.on("status", ({ status }) => {
  // 'connecting' | 'connected' | 'disconnected'
});

// Authentication
provider.on("authenticationFailed", ({ reason }) => {
  console.error("Auth failed:", reason);
});
```

## Production deployment

### Docker

```dockerfile theme={null}
FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --production

COPY . .

EXPOSE 1234
CMD ["node", "server.js"]
```

### With Redis (scaling)

```typescript theme={null}
import { Server } from "@hocuspocus/server";
import { Redis } from "@hocuspocus/extension-redis";

Server.configure({
  extensions: [
    new Redis({
      host: "localhost",
      port: 6379,
    }),
  ],
});
```

## Resources

<CardGroup cols={2}>
  <Card title="Hocuspocus Docs" icon="book" href="https://tiptap.dev/docs/hocuspocus">
    Official documentation
  </Card>

  <Card title="Working example" icon="github" href="https://github.com/superdoc-dev/superdoc/tree/main/examples/editor/collaboration/providers/hocuspocus">
    Complete source code
  </Card>
</CardGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Client Configuration" icon="settings" href="/editor/collaboration/configuration">
    All SuperDoc collaboration options
  </Card>

  <Card title="Self-hosted overview" icon="server" href="/guides/collaboration/self-hosted-overview">
    Compare Hocuspocus, YHub, and the SuperDoc Yjs reference server
  </Card>
</CardGroup>
