> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ueberdosis/tiptap/llms.txt
> Use this file to discover all available pages before exploring further.

# Collaborative Editing

> Enable real-time collaboration in your Tiptap editor using Yjs

# Collaborative Editing

Tiptap provides first-class support for real-time collaborative editing through the Collaboration extension, which is powered by Yjs, a battle-tested CRDT (Conflict-free Replicated Data Type) framework.

## Installation

First, install the required packages:

<CodeGroup>
  ```bash npm theme={null}
  npm install @tiptap/extension-collaboration yjs
  ```

  ```bash yarn theme={null}
  yarn add @tiptap/extension-collaboration yjs
  ```

  ```bash pnpm theme={null}
  pnpm add @tiptap/extension-collaboration yjs
  ```
</CodeGroup>

## Basic Setup

The Collaboration extension requires a Yjs document to synchronize content between users.

<CodeGroup>
  ```typescript Basic Collaboration theme={null}
  import { Editor } from '@tiptap/core'
  import StarterKit from '@tiptap/starter-kit'
  import Collaboration from '@tiptap/extension-collaboration'
  import * as Y from 'yjs'

  // Create a Yjs document
  const ydoc = new Y.Doc()

  const editor = new Editor({
    extensions: [
      StarterKit.configure({
        // Disable the default history extension
        // (collaboration comes with its own)
        history: false,
      }),
      Collaboration.configure({
        document: ydoc,
      }),
    ],
  })
  ```
</CodeGroup>

## Understanding the Collaboration Extension

The Collaboration extension provides undo/redo functionality and real-time synchronization.

<CodeGroup>
  ```typescript Collaboration Options theme={null}
  export interface CollaborationOptions {
    // An initialized Y.js document
    document?: Doc | null
    
    // Name of a Y.js fragment (for multiple fields in one document)
    field?: string
    
    // A raw Y.js fragment (alternative to document + field)
    fragment?: XmlFragment | null
    
    // The collaboration provider
    provider?: any | null
    
    // Callback when content is initially rendered
    onFirstRender?: () => void
    
    // Options for the Yjs sync plugin
    ySyncOptions?: YSyncOpts
    
    // Options for the Yjs undo plugin
    yUndoOptions?: YUndoOpts
  }
  ```
</CodeGroup>

Source: `packages/extension-collaboration/src/collaboration.ts:41`

## Collaboration Commands

The extension adds undo/redo commands that work with collaborative editing.

<CodeGroup>
  ```typescript Collaboration Commands theme={null}
  // Undo recent changes
  editor.commands.undo()

  // Redo changes
  editor.commands.redo()

  // Check if undo is available
  if (editor.can().undo()) {
    editor.commands.undo()
  }

  // Check if redo is available
  if (editor.can().redo()) {
    editor.commands.redo()
  }
  ```
</CodeGroup>

Source: `packages/extension-collaboration/src/collaboration.ts:121`

## Keyboard Shortcuts

The extension provides standard keyboard shortcuts.

<CodeGroup>
  ```typescript Keyboard Shortcuts theme={null}
  // Undo
  'Mod-z': () => this.editor.commands.undo()

  // Redo
  'Mod-y': () => this.editor.commands.redo()
  'Shift-Mod-z': () => this.editor.commands.redo()
  ```
</CodeGroup>

Source: `packages/extension-collaboration/src/collaboration.ts:160`

## WebSocket Providers

To sync between multiple clients, you need a provider. Here are popular options:

### y-websocket

<CodeGroup>
  ```bash Installation theme={null}
  npm install y-websocket
  ```

  ```typescript WebSocket Provider theme={null}
  import { Editor } from '@tiptap/core'
  import StarterKit from '@tiptap/starter-kit'
  import Collaboration from '@tiptap/extension-collaboration'
  import { WebsocketProvider } from 'y-websocket'
  import * as Y from 'yjs'

  const ydoc = new Y.Doc()

  // Connect to WebSocket server
  const provider = new WebsocketProvider(
    'ws://localhost:1234', // WebSocket URL
    'my-document',         // Document name
    ydoc                   // Yjs document
  )

  const editor = new Editor({
    extensions: [
      StarterKit.configure({
        history: false,
      }),
      Collaboration.configure({
        document: ydoc,
      }),
    ],
  })

  // Clean up on destroy
  editor.on('destroy', () => {
    provider.destroy()
  })
  ```
</CodeGroup>

### y-webrtc

For peer-to-peer connections without a server.

<CodeGroup>
  ```bash Installation theme={null}
  npm install y-webrtc
  ```

  ```typescript WebRTC Provider theme={null}
  import { Editor } from '@tiptap/core'
  import StarterKit from '@tiptap/starter-kit'
  import Collaboration from '@tiptap/extension-collaboration'
  import { WebrtcProvider } from 'y-webrtc'
  import * as Y from 'yjs'

  const ydoc = new Y.Doc()

  // Connect via WebRTC
  const provider = new WebrtcProvider(
    'my-document-name',  // Room name
    ydoc,                // Yjs document
    {
      signaling: ['wss://signaling.yjs.dev'],
    }
  )

  const editor = new Editor({
    extensions: [
      StarterKit.configure({
        history: false,
      }),
      Collaboration.configure({
        document: ydoc,
      }),
    ],
  })
  ```
</CodeGroup>

## Collaboration Cursor

Show where other users are editing with the CollaborationCursor extension.

<CodeGroup>
  ```bash Installation theme={null}
  npm install @tiptap/extension-collaboration-cursor
  ```

  ```typescript Collaboration Cursor theme={null}
  import { Editor } from '@tiptap/core'
  import StarterKit from '@tiptap/starter-kit'
  import Collaboration from '@tiptap/extension-collaboration'
  import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
  import { WebsocketProvider } from 'y-websocket'
  import * as Y from 'yjs'

  const ydoc = new Y.Doc()
  const provider = new WebsocketProvider('ws://localhost:1234', 'my-doc', ydoc)

  const editor = new Editor({
    extensions: [
      StarterKit.configure({
        history: false,
      }),
      Collaboration.configure({
        document: ydoc,
      }),
      CollaborationCursor.configure({
        provider: provider,
        user: {
          name: 'John Doe',
          color: '#6366f1',
        },
      }),
    ],
  })
  ```
</CodeGroup>

## Styling Collaboration Cursors

<CodeGroup>
  ```css Cursor Styles theme={null}
  /* Collaboration cursor */
  .collaboration-cursor__caret {
    border-left: 2px solid;
    border-color: currentColor;
    margin-left: -1px;
    margin-right: -1px;
    pointer-events: none;
    position: relative;
    word-break: normal;
  }

  /* Cursor label */
  .collaboration-cursor__label {
    background-color: currentColor;
    border-radius: 0.25rem;
    color: white;
    font-size: 0.75rem;
    font-weight: 600;
    left: -1px;
    line-height: 1;
    padding: 0.125rem 0.375rem;
    position: absolute;
    top: -1.5rem;
    user-select: none;
    white-space: nowrap;
  }
  ```
</CodeGroup>

## Multiple Fields

You can use multiple editor instances with the same Yjs document.

<CodeGroup>
  ```typescript Multiple Fields theme={null}
  import * as Y from 'yjs'

  const ydoc = new Y.Doc()

  // Title editor
  const titleEditor = new Editor({
    extensions: [
      StarterKit.configure({ history: false }),
      Collaboration.configure({
        document: ydoc,
        field: 'title',  // Use 'title' fragment
      }),
    ],
  })

  // Content editor
  const contentEditor = new Editor({
    extensions: [
      StarterKit.configure({ history: false }),
      Collaboration.configure({
        document: ydoc,
        field: 'content',  // Use 'content' fragment
      }),
    ],
  })
  ```
</CodeGroup>

Source: `packages/extension-collaboration/src/collaboration.ts:49`

## Offline Support

Yjs can work offline and sync when reconnected.

<CodeGroup>
  ```typescript Offline Handling theme={null}
  import { WebsocketProvider } from 'y-websocket'
  import * as Y from 'yjs'

  const ydoc = new Y.Doc()
  const provider = new WebsocketProvider('ws://localhost:1234', 'my-doc', ydoc)

  // Listen for connection status
  provider.on('status', (event: { status: string }) => {
    if (event.status === 'connected') {
      console.log('Connected to server')
    } else if (event.status === 'disconnected') {
      console.log('Disconnected from server')
    }
  })

  // Listen for sync events
  provider.on('sync', (isSynced: boolean) => {
    if (isSynced) {
      console.log('Document synced')
    }
  })
  ```
</CodeGroup>

## Persistence

Persist collaborative documents to a database.

<CodeGroup>
  ```typescript Persistence with y-indexeddb theme={null}
  import { IndexeddbPersistence } from 'y-indexeddb'
  import * as Y from 'yjs'

  const ydoc = new Y.Doc()

  // Persist to IndexedDB
  const persistence = new IndexeddbPersistence('my-document', ydoc)

  persistence.on('synced', () => {
    console.log('Document loaded from IndexedDB')
  })
  ```

  ```typescript Server-side Persistence theme={null}
  import * as Y from 'yjs'
  import { WebsocketProvider } from 'y-websocket'

  // On the server
  import * as fs from 'fs'

  const ydoc = new Y.Doc()

  // Load from file
  if (fs.existsSync('document.yjs')) {
    const data = fs.readFileSync('document.yjs')
    Y.applyUpdate(ydoc, data)
  }

  // Save on changes
  ydoc.on('update', (update: Uint8Array) => {
    fs.writeFileSync('document.yjs', Y.encodeStateAsUpdate(ydoc))
  })
  ```
</CodeGroup>

## React Example

Complete React example with collaboration.

<CodeGroup>
  ```typescript React Collaboration theme={null}
  import { useEditor, EditorContent } from '@tiptap/react'
  import StarterKit from '@tiptap/starter-kit'
  import Collaboration from '@tiptap/extension-collaboration'
  import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
  import { WebsocketProvider } from 'y-websocket'
  import * as Y from 'yjs'
  import { useEffect, useState } from 'react'

  export default function CollaborativeEditor({ room, user }) {
    const [provider, setProvider] = useState<WebsocketProvider | null>(null)
    
    const editor = useEditor({
      extensions: [
        StarterKit.configure({
          history: false,
        }),
        Collaboration.configure({
          document: provider?.document,
        }),
        CollaborationCursor.configure({
          provider: provider,
          user: {
            name: user.name,
            color: user.color,
          },
        }),
      ],
    })
    
    useEffect(() => {
      const ydoc = new Y.Doc()
      const websocketProvider = new WebsocketProvider(
        'ws://localhost:1234',
        room,
        ydoc
      )
      
      setProvider(websocketProvider)
      
      return () => {
        websocketProvider?.destroy()
        ydoc?.destroy()
      }
    }, [room])
    
    if (!editor || !provider) {
      return <div>Loading...</div>
    }
    
    return (
      <div>
        <div className="collaboration-status">
          {provider.wsconnected ? 'Connected' : 'Disconnected'}
        </div>
        <EditorContent editor={editor} />
      </div>
    )
  }
  ```
</CodeGroup>

## Conflict Resolution

Yjs automatically handles conflicts using CRDTs.

<CodeGroup>
  ```typescript Understanding Conflicts theme={null}
  // User A types "Hello"
  // User B types "World" at the same position
  // Result: "HelloWorld" or "WorldHello" (deterministic)

  // Yjs ensures:
  // 1. All clients converge to the same state
  // 2. No data loss
  // 3. Causal consistency
  // 4. Intention preservation
  ```
</CodeGroup>

## Performance Optimization

<CodeGroup>
  ```typescript Optimize Collaboration theme={null}
  import Collaboration from '@tiptap/extension-collaboration'

  const editor = new Editor({
    extensions: [
      Collaboration.configure({
        document: ydoc,
        
        // Undo/redo options
        yUndoOptions: {
          // Number of milliseconds to group changes
          trackedOrigins: [],
          // Capture timeout in ms
          captureTimeout: 500,
        },
        
        // Sync options
        ySyncOptions: {
          // Custom colors for collaboration cursors
          colors: [
            { light: '#6366f1', dark: '#4f46e5' },
            { light: '#ec4899', dark: '#db2777' },
            { light: '#14b8a6', dark: '#0d9488' },
          ],
        },
      }),
    ],
  })
  ```
</CodeGroup>

Source: `packages/extension-collaboration/src/collaboration.ts:73`

## Disabling Collaboration

Temporarily disable collaboration to prevent syncing.

<CodeGroup>
  ```typescript Disable Collaboration theme={null}
  // Check if disabled
  if (editor.storage.collaboration.isDisabled) {
    console.log('Collaboration is disabled')
  }

  // The extension can disable itself if content errors occur
  editor.on('contentError', ({ disableCollaboration }) => {
    // This will disable collaboration and prevent syncing
    disableCollaboration()
  })
  ```
</CodeGroup>

Source: `packages/extension-collaboration/src/collaboration.ts:12`

<Warning>
  The Collaboration extension is not compatible with the History extension. The collaboration extension provides its own undo/redo functionality.
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="TypeScript" icon="code" href="/guides/typescript">
    Add type safety to your collaborative editor
  </Card>

  <Card title="Styling" icon="palette" href="/guides/styling">
    Style collaboration cursors and indicators
  </Card>
</CardGroup>
