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

# Collaboration

> Real-time collaboration for Yoopta Editor using Yjs CRDT

## Overview

The `@yoopta/collaboration` package enables real-time collaborative editing in Yoopta Editor. Multiple users can edit the same document simultaneously with automatic conflict resolution powered by [Yjs](https://yjs.dev/) CRDT.

The package provides:

* **`withCollaboration`** extension — connects your editor to a collaboration server
* **Collaborative undo/redo** — Cmd+Z only undoes your own changes, not other users'
* **Remote cursors** — see where other users are editing
* **Awareness** — track connected users and their presence
* **React hooks** — access collaboration state in your components

The collaboration client is **fully open-source** and works with any Yjs-compatible WebSocket server. You can self-host with your own infrastructure or use [Yoopta Cloud](#yoopta-cloud) for a managed solution with version history, permissions, and more.

## Installation

```bash theme={null}
npm install @yoopta/collaboration
```

<Note>
  `@yoopta/collaboration` requires `@yoopta/editor` >= 6.0.0 and React >= 18.2.0 as peer
  dependencies.
</Note>

## Quick Start

Wrap your editor with `withCollaboration` — similar to how `withMentions` works:

```tsx theme={null}
import { useMemo, useEffect } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import { withCollaboration, RemoteCursors } from '@yoopta/collaboration';
import { PLUGINS } from './plugins';
import { MARKS } from './marks';

export default function CollaborativeEditor() {
  const editor = useMemo(
    () =>
      withCollaboration(createYooptaEditor({ plugins: PLUGINS, marks: MARKS }), {
        url: 'wss://collab.yoopta.cloud',
        roomId: 'document-123',
        token: 'your-auth-token',
        user: {
          id: 'user-1',
          name: 'Alice',
          color: '#e06c75',
        },
      }),
    [],
  );

  useEffect(() => {
    editor.collaboration.connect();

    return () => {
      editor.collaboration.disconnect();
    };
  }, [editor.collaboration]);

  return (
    <YooptaEditor editor={editor} onChange={(value) => console.log(value)}>
      <RemoteCursors />
      {/* Your other UI components */}
    </YooptaEditor>
  );
}
```

<Warning>
  Always call `editor.collaboration.destroy()` on cleanup to properly close the WebSocket connection
  and remove awareness states.
</Warning>

## Configuration

### `withCollaboration(editor, config)`

Extends a `YooEditor` instance with collaboration capabilities. Returns a `CollaborationYooEditor`.

<ParamField path="editor" type="YooEditor" required>
  The editor instance from `createYooptaEditor()`.
</ParamField>

<ParamField path="config" type="CollaborationConfig" required>
  Configuration object for the collaboration session.
</ParamField>

### CollaborationConfig

<ParamField path="url" type="string" required>
  WebSocket server URL. Example: `"wss://collab.yoopta.cloud"`
</ParamField>

<ParamField path="roomId" type="string" required>
  Unique identifier for the document/room. All users with the same `roomId` will collaborate on the
  same document.
</ParamField>

<ParamField path="user" type="CollaborationUser" required>
  Information about the current user. Used for cursor labels and presence indicators.

  ```ts theme={null}
  {
    id: string;       // Unique user identifier
    name: string;     // Display name (shown on cursors)
    color: string;    // Cursor color (hex, e.g. "#e06c75")
    avatar?: string;  // Optional avatar URL
  }
  ```
</ParamField>

<ParamField path="token" type="string">
  Authentication token sent to the server on connection. Use this to verify users and check
  subscription access on the server side.
</ParamField>

<ParamField path="initialValue" type="YooptaContentValue">
  Initial content to seed the document if no remote state exists. When the first user connects to an
  empty room, this value populates the shared document.
</ParamField>

<ParamField path="connect" type="boolean" default="true">
  Whether to connect to the server immediately. Set to `false` to connect manually later via
  `editor.collaboration.connect()`.
</ParamField>

<ParamField path="document" type="Y.Doc">
  Optional: provide your own Yjs `Y.Doc` instance. If not provided, one is created automatically.
  Useful for advanced integrations or testing.
</ParamField>

## Editor API

After applying `withCollaboration`, the editor gains an `editor.collaboration` namespace:

### `editor.collaboration.state`

Read-only access to the current collaboration state.

```ts theme={null}
type CollaborationState = {
  status: ConnectionStatus; // 'disconnected' | 'connecting' | 'connected' | 'error'
  connectedUsers: CollaborationUser[]; // All users currently in the room
  document: Y.Doc | null; // The Yjs document instance
  isSynced: boolean; // Whether initial sync with server is complete
};
```

### `editor.collaboration.connect()`

Manually connect to the WebSocket server. Only needed if `connect: false` was passed in config.

```ts theme={null}
editor.collaboration.connect();
```

### `editor.collaboration.disconnect()`

Disconnect from the server. The editor remains usable for local editing.

```ts theme={null}
editor.collaboration.disconnect();
```

### `editor.collaboration.destroy()`

Disconnect and clean up all resources (WebSocket, observers, awareness). Call this on component unmount.

```ts theme={null}
useEffect(() => {
  return () => editor.collaboration.destroy();
}, [editor]);
```

### `editor.collaboration.getDocument()`

Returns the underlying `Y.Doc` instance for advanced use cases.

```ts theme={null}
const doc = editor.collaboration.getDocument();
```

## Components

### `<RemoteCursors />`

Renders visual indicators showing which block each remote user is currently editing.

```tsx theme={null}
import { RemoteCursors } from '@yoopta/collaboration';

<YooptaEditor editor={editor}>
  <RemoteCursors />
</YooptaEditor>;
```

Must be rendered as a child of `<YooptaEditor>`. Displays a colored bar and name label next to the block each remote user is editing.

## Hooks

All hooks must be used within a `<YooptaEditor>` component that has collaboration enabled.

### `useCollaboration()`

Returns the full collaboration state. Re-renders on any state change.

```tsx theme={null}
import { useCollaboration } from '@yoopta/collaboration';

function StatusBar() {
  const { status, connectedUsers, isSynced } = useCollaboration();

  return (
    <div>
      <span>{status}</span>
      <span>{connectedUsers.length} online</span>
      {!isSynced && <span>Syncing...</span>}
    </div>
  );
}
```

### `useConnectionStatus()`

Returns only the connection status string. Lighter than `useCollaboration()` when you only need the status.

```tsx theme={null}
import { useConnectionStatus } from '@yoopta/collaboration';

function ConnectionIndicator() {
  const status = useConnectionStatus();

  return (
    <div
      style={{
        width: 8,
        height: 8,
        borderRadius: '50%',
        backgroundColor:
          status === 'connected' ? '#4ade80' : status === 'connecting' ? '#facc15' : '#ef4444',
      }}
    />
  );
}
```

### `useRemoteCursors()`

Returns an array of remote cursor data for all connected users.

```tsx theme={null}
import { useRemoteCursors } from '@yoopta/collaboration';

function UserPresence() {
  const cursors = useRemoteCursors();

  return (
    <div>
      {cursors.map((cursor) => (
        <div key={cursor.clientId} style={{ color: cursor.user.color }}>
          {cursor.user.name} - editing block {cursor.blockId}
        </div>
      ))}
    </div>
  );
}
```

## Deferred Connection

If you need to connect after the editor is rendered (e.g., after authentication), set `connect: false`:

```tsx theme={null}
const editor = useMemo(
  () =>
    withCollaboration(createYooptaEditor({ plugins: PLUGINS, marks: MARKS }), {
      url: 'wss://collab.yoopta.cloud',
      roomId: 'doc-123',
      user: { id: 'user-1', name: 'Alice', color: '#e06c75' },
      connect: false, // Don't connect yet
    }),
  [],
);

// Connect later, e.g. after auth
function handleLogin(token: string) {
  editor.collaboration.connect();
}
```

## Custom Y.Doc

For advanced use cases (e.g., testing two editors in the same page), you can provide your own `Y.Doc`:

```tsx theme={null}
import * as Y from 'yjs';

const doc = new Y.Doc();

const editor = useMemo(
  () =>
    withCollaboration(createYooptaEditor({ plugins: PLUGINS, marks: MARKS }), {
      url: 'wss://collab.yoopta.cloud',
      roomId: 'doc-123',
      user: { id: 'user-1', name: 'Alice', color: '#e06c75' },
      document: doc,
    }),
  [],
);
```

## Self-Hosted Setup

`@yoopta/collaboration` uses Yjs only as a **sync layer** — it does not store your documents. Your content remains standard Yoopta JSON (`YooptaContentValue`) that you store in your own database.

```
┌─────────────┐     load       ┌──────────────────┐      sync       ┌─────────────┐
│ Your DB     │ ──────────────►│  Yoopta Editor    │◄───────────────►│  Other      │
│ (Postgres,  │  initialValue  │  + Y.Doc          │  WebSocket      │  Clients    │
│  Mongo, ..) │◄──────────────┤                    │  (y-protocols)  │             │
└─────────────┘  save on       └────────┬───────────┘                └─────────────┘
                 change                 │
                                        │
                                 ┌──────▼──────────┐
                                 │  Your WebSocket  │
                                 │  Server          │
                                 └─────────────────┘
```

The Y.Doc is **ephemeral** — it only lives during the collaboration session. When all users disconnect, it can be discarded. Your database is the source of truth.

### Persisting to Your Database

Load content from your database as `initialValue`, and save back on change:

```tsx theme={null}
import { useMemo, useEffect, useCallback } from 'react';
import { debounce } from 'lodash'; // or your preferred debounce
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import { withCollaboration, RemoteCursors } from '@yoopta/collaboration';

export default function CollaborativeEditor({ documentId, savedContent, currentUser }) {
  const editor = useMemo(
    () =>
      withCollaboration(createYooptaEditor({ plugins: PLUGINS, marks: MARKS }), {
        url: 'ws://localhost:4000', // Your own server
        roomId: documentId,
        user: currentUser,
        initialValue: savedContent, // Load from your DB
      }),
    [],
  );

  // Save to your database on change
  const handleChange = useCallback(
    debounce((value) => {
      fetch(`/api/documents/${documentId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(value),
      });
    }, 2000),
    [documentId],
  );

  useEffect(() => {
    editor.collaboration.connect();

    return () => {
      editor.collaboration.disconnect();
    };
  }, [editor.collaboration]);

  return (
    <YooptaEditor editor={editor} onChange={handleChange}>
      <RemoteCursors />
    </YooptaEditor>
  );
}
```

<Note>
  The `initialValue` is only used when the first user connects to an empty room. Once the Y.Doc has
  content (from any client), subsequent connections load from the Y.Doc state, not from
  `initialValue`.
</Note>

### Running Your Own WebSocket Server

Any Yjs-compatible WebSocket server works. The simplest option is [y-websocket](https://github.com/yjs/y-websocket):

```bash theme={null}
npm install y-websocket
```

```js theme={null}
// server.js
import { WebSocketServer } from 'ws';
import { setupWSConnection } from 'y-websocket/bin/utils';

const wss = new WebSocketServer({ port: 4000 });

wss.on('connection', (ws, req) => {
  // req.url is "/<roomId>"
  setupWSConnection(ws, req);
});

console.log('Yjs WebSocket server running on ws://localhost:4000');
```

For production, consider [Hocuspocus](https://tiptap.dev/hocuspocus) which adds authentication, persistence hooks, and webhook support out of the box.

<Tip>
  For small teams (2-5 concurrent editors), a single y-websocket process is more than sufficient. No
  special infrastructure needed.
</Tip>

## Yoopta Cloud

For teams that don't want to run their own collaboration infrastructure, **Yoopta Cloud** provides a managed solution:

* Managed WebSocket infrastructure with automatic scaling
* Built-in document persistence and version history
* Authentication and room-level permissions
* Comments and threads
* REST API for document access
* Webhooks for integrations (search indexing, backups, workflows)
* Analytics dashboard

```tsx theme={null}
// Using Yoopta Cloud — just change the URL and add your API key
const editor = useMemo(
  () =>
    withCollaboration(createYooptaEditor({ plugins: PLUGINS, marks: MARKS }), {
      url: 'wss://collab.yoopta.cloud',
      roomId: 'document-123',
      token: 'your-api-key',
      user: { id: 'user-1', name: 'Alice', color: '#e06c75' },
    }),
  [],
);
```

<Info>
  Yoopta Cloud is coming soon. The self-hosted path described above will always remain free and
  open-source.
</Info>

## How It Works

Under the hood, `@yoopta/collaboration` maintains a bidirectional binding between the Yoopta editor and a Yjs shared document:

```
┌──────────────────────┐        ┌──────────────────────┐
│   Yoopta Editor      │        │   Y.Doc              │
│                      │        │                      │
│  editor.children ◄──►│ binding│ Y.Array("blockOrder")│
│  block ordering      │◄──────►│ Y.Map("blockMeta")   │
│  block metadata      │        │ Y.Map("blockContents")│
│  Slate content       │        │                      │
└──────────────────────┘        └──────┬───────────────┘
                                       │
                                       │ y-protocols (WebSocket)
                                       │
                                ┌──────▼───────────────┐
                                │   Collab Server       │
                                └───────────────────────┘
```

* **Local changes** flow through `editor.applyTransforms` and are automatically synced to the Y.Doc, then sent to the server via WebSocket.
* **Remote changes** arrive via WebSocket, update the Y.Doc, and are applied to the editor without entering the local undo/redo history.
* **Conflict resolution** is handled by the Yjs CRDT — concurrent edits are merged automatically at the character level.
* **Undo/redo** uses Yjs `UndoManager` to selectively reverse only local changes (see below).

## Undo / Redo

When collaboration is active, `editor.undo()` and `editor.redo()` (Cmd+Z / Cmd+Shift+Z) automatically use Yjs `UndoManager` instead of the default history stack. This means:

* **Only your changes are undone** — if Alice types "Hello" and Bob types "World", Alice pressing Cmd+Z removes only "Hello". Bob's text stays intact.
* **Works at all levels** — text edits, block insertions, deletions, splits, merges, formatting, and metadata changes are all tracked.
* **No extra setup needed** — `withCollaboration` overrides undo/redo automatically. When collaboration is destroyed, the default history behavior is restored.

Rapid edits (e.g., continuous typing) are batched into a single undo step, similar to how undo works in Google Docs or Notion.

<Note>
  The initial document content (loaded from the server or seeded via `initialValue`) is not undoable
  — only edits made after the document loads can be undone.
</Note>

## TypeScript

All types are exported from the package:

```ts theme={null}
import type {
  CollaborationConfig,
  CollaborationUser,
  CollaborationState,
  ConnectionStatus,
  RemoteCursorData,
  CollaborationAPI,
  CollaborationYooEditor,
} from '@yoopta/collaboration';
```

## WebSocketProvider

For advanced server integrations, the `WebSocketProvider` class is exported:

```ts theme={null}
import { WebSocketProvider } from '@yoopta/collaboration';
```

This is the transport layer used internally. It implements the Yjs sync and awareness protocols over WebSocket with automatic reconnection and authentication support.
