Skip to main content
The Collaboration extension enables real-time collaborative editing in Tiptap using Yjs. It synchronizes content across multiple users and includes built-in undo/redo functionality that works seamlessly in collaborative environments.
The Collaboration extension comes with its own history implementation and is not compatible with the @tiptap/extension-undo-redo extension. Make sure to remove the UndoRedo extension if you’re using Collaboration.

Installation

npm install @tiptap/extension-collaboration yjs
You’ll also need a Yjs provider to sync data between clients. Popular options include:
npm install y-websocket  # WebSocket provider
npm install y-webrtc     # WebRTC provider

Basic Usage

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Collaboration from '@tiptap/extension-collaboration'
import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'

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

// Create a provider for syncing (WebSocket example)
const provider = new WebsocketProvider(
  'ws://localhost:1234',
  'document-name',
  ydoc
)

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

Configuration Options

document
Y.Doc
An initialized Y.js document. This is the shared data structure that will be synchronized across all clients.Example:
import * as Y from 'yjs'

const ydoc = new Y.Doc()

Collaboration.configure({
  document: ydoc,
})
field
string
Name of the Y.js fragment to use. You can use different fields to sync multiple editors with one Y.js document.Default: 'default'
Collaboration.configure({
  document: ydoc,
  field: 'body', // Store editor content in 'body' field
})

// You could have another editor using the same document
Collaboration.configure({
  document: ydoc,
  field: 'sidebar', // Store different content in 'sidebar' field
})
fragment
XmlFragment
A raw Y.js fragment. Use this instead of document and field if you want direct control over the fragment.
const ydoc = new Y.Doc()
const fragment = ydoc.getXmlFragment('custom-fragment')

Collaboration.configure({
  fragment: fragment,
})
provider
any
The collaboration provider instance (e.g., WebsocketProvider, WebrtcProvider). While not required by the extension itself, storing it here makes it accessible via the extension.Default: null
import { WebsocketProvider } from 'y-websocket'

const provider = new WebsocketProvider('ws://localhost:1234', 'doc', ydoc)

Collaboration.configure({
  document: ydoc,
  provider: provider,
})
onFirstRender
() => void
Callback fired when the content from Yjs is initially rendered to Tiptap. Useful for showing a loading state until the document is ready.
Collaboration.configure({
  document: ydoc,
  onFirstRender: () => {
    console.log('Document loaded and rendered')
    hideLoadingSpinner()
  },
})
ySyncOptions
object
Options passed to the Y.js sync plugin. See y-prosemirror documentation for available options.
Collaboration.configure({
  document: ydoc,
  ySyncOptions: {
    colors: [
      { light: '#ecd44433', dark: '#ecd444' },
      { light: '#ee635233', dark: '#ee6352' },
    ],
  },
})
yUndoOptions
object
Options passed to the Y.js undo plugin.
Collaboration.configure({
  document: ydoc,
  yUndoOptions: {
    trackedOrigins: new Set([ydoc.clientID]),
  },
})

Commands

undo
command
Undo recent changes.Keyboard shortcut: Cmd/Ctrl + Z
editor.commands.undo()
redo
command
Reapply reverted changes.Keyboard shortcuts: Cmd/Ctrl + Shift + Z or Cmd/Ctrl + Y
editor.commands.redo()

Storage

isDisabled
boolean
Whether collaboration is currently disabled. This is automatically set when collaboration encounters a content error.
// Check if collaboration is disabled
if (editor.storage.collaboration.isDisabled) {
  console.log('Collaboration has been disabled')
}

Advanced Examples

Complete Collaborative Editor

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 * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'

const ydoc = new Y.Doc()

const provider = new WebsocketProvider(
  'ws://localhost:1234',
  'my-document',
  ydoc
)

const editor = new Editor({
  extensions: [
    StarterKit.configure({
      history: false, // Important: disable default history
    }),
    Collaboration.configure({
      document: ydoc,
    }),
    CollaborationCursor.configure({
      provider: provider,
      user: {
        name: 'John Doe',
        color: '#f783ac',
      },
    }),
  ],
})

Multiple Editors with One Document

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

// Main editor
const mainEditor = new Editor({
  element: document.querySelector('#main-editor'),
  extensions: [
    StarterKit.configure({ history: false }),
    Collaboration.configure({
      document: ydoc,
      field: 'main',
    }),
  ],
})

// Sidebar editor
const sidebarEditor = new Editor({
  element: document.querySelector('#sidebar-editor'),
  extensions: [
    StarterKit.configure({ history: false }),
    Collaboration.configure({
      document: ydoc,
      field: 'sidebar',
    }),
  ],
})

Loading State

let isDocumentLoaded = false

const editor = new Editor({
  editable: false, // Start in read-only mode
  extensions: [
    StarterKit.configure({ history: false }),
    Collaboration.configure({
      document: ydoc,
      onFirstRender: () => {
        isDocumentLoaded = true
        editor.setEditable(true)
        hideLoadingSpinner()
      },
    }),
  ],
})

showLoadingSpinner()

Content Error Handling

const editor = new Editor({
  enableContentCheck: true,
  extensions: [
    StarterKit.configure({ history: false }),
    Collaboration.configure({
      document: ydoc,
    }),
  ],
  onContentError: ({ editor, error, disableCollaboration }) => {
    console.error('Content validation error:', error)
    
    // Option 1: Disable collaboration to prevent further issues
    disableCollaboration()
    
    // Option 2: Show error to user
    showErrorNotification(
      'Document contains invalid content. Collaboration has been disabled.'
    )
    
    // Option 3: Attempt recovery
    editor.commands.setContent('<p>Content was reset due to an error</p>')
  },
})

Custom Provider Setup

import { WebrtcProvider } from 'y-webrtc'

const ydoc = new Y.Doc()

// WebRTC provider for peer-to-peer collaboration
const provider = new WebrtcProvider('my-document', ydoc, {
  signaling: ['wss://signaling.example.com'],
  password: 'optional-room-password',
})

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

// Clean up on unmount
window.addEventListener('beforeunload', () => {
  provider.destroy()
  ydoc.destroy()
})

Custom Undo/Redo Tracking

Collaboration.configure({
  document: ydoc,
  yUndoOptions: {
    // Only track changes from this user
    trackedOrigins: new Set([ydoc.clientID]),
    // Capture timeout in ms
    captureTimeout: 500,
  },
})

Keyboard Shortcuts

ShortcutCommandDescription
Cmd/Ctrl + ZundoUndo the last change
Cmd/Ctrl + Shift + ZredoRedo the last undone change
Cmd/Ctrl + YredoRedo the last undone change

Storage Access

// Check if collaboration is disabled
const isDisabled = editor.storage.collaboration.isDisabled

// Access the Y.js document
const ydoc = editor.extensionManager.extensions
  .find(ext => ext.name === 'collaboration')
  .options.document

Utilities

The Collaboration extension adds utility methods to the editor:
// Get updated position after transaction
const newPosition = editor.utils.getUpdatedPosition(oldPosition, transaction)

// Create a mappable position that tracks across transactions
const mappablePos = editor.utils.createMappablePosition(position)

Source Code

View the source code on GitHub:

Build docs developers (and LLMs) love