Skip to main content
Enable real-time collaborative editing on Tiptap Editors. The collaboration editing engine is built on top of Yjs and Velt SDK.

Prerequisites

  • Node.js (v14 or higher)
  • React (v16.8 or higher for hooks)
  • A Velt account with an API key (sign up)
  • Optional: TypeScript for type safety

Setup

Step 1: Install Dependencies

Install the required packages:

Step 2: Setup Velt

Initialize the Velt client by following the Velt Setup Docs. This is required for the collaboration engine to work.

Step 3: Initialize Collaborative Editor

  • Initialize the collaboration manager and use it to create the Tiptap editor.
  • Pass an editorId to uniquely identify each editor instance you have in your app. This is especially important when you have multiple editors in your app.
Use the useCollaboration hook to manage the entire CRDT lifecycle. It creates a CollaborationManager, initializes the CRDT store, and returns a TipTap Extension for collaboration and cursor tracking.
Disable TipTap’s built-in undo/redo (undoRedo: false in StarterKit), CRDT manages document state.

Step 4: Add CSS for Collaboration Cursor

  • Add the following CSS to your app to style the collaboration cursor.

Step 5: Status Monitoring (Optional)

Monitor the connection status and sync state to display UI indicators.
The useCollaboration hook returns reactive status, isSynced, and error state:

Step 6: Version Management (Optional)

Save and restore named snapshots of the document state.
The manager returned by useCollaboration provides version management methods:

Step 7: Force Reset Initial Content (Optional)

By default, initialContent is applied exactly once, only when the document is brand new. Pass forceResetInitialContent: true to always overwrite remote data with initialContent on initialization. Useful for “reset to template” workflows.

Step 8: CRDT Event Subscription (Optional)

Listen for real-time sync events using getCrdtElement().on() from the Velt client.

Step 9: Custom Encryption (Optional)

Encrypt CRDT data before it’s stored in Velt by registering a custom encryption provider. For CRDT methods, input data is of type Uint8Array | number[].
See also: setEncryptionProvider() · VeltEncryptionProvider · EncryptConfig · DecryptConfig

Step 10: Error Handling (Optional)

Pass an onError callback to handle initialization or runtime errors. The error reactive state is also available for rendering.

Step 11: Access Yjs Internals (Advanced)

The manager exposes escape hatches for direct Yjs manipulation when needed.

Step 12: Cleanup

Cleanup is automatic, when the TipTap editor is destroyed, the extension’s onDestroy hook triggers manager.destroy(), which cascades cleanup to the store, provider, and all listeners. You can also call manager.destroy() manually:

Notes

  • Unique editorId: Use a unique editorId per editor instance.
  • Disable history: Turn off Tiptap history / undoRedo when using collaboration.
  • Auth required: Ensure the Velt client is initialized and user is available before starting.

Testing and Debugging

To test collaboration:
  1. Login two unique users on two browser profiles and open the same page in both.
  2. Edit in one window and verify changes and cursors appear in the other.
Common issues:
  • Cursors not appearing: Ensure each editor has a unique editorId and users are authenticated. Also ensure that you are logged in with two different users in two different browser profiles.
  • Editor not loading: Confirm the Velt client is initialized and the API key is valid.
  • Desynced content: Make sure Tiptap history / undoRedo is disabled when using collaboration.
Enable browser console warnings to see detailed debugging information. The Velt SDK logs helpful warnings and errors to the console that can help you troubleshoot issues quickly. You can also use the VeltCrdtStoreMap debugging interface to inspect and monitor your CRDT stores in real-time.

Complete Example

A complete collaborative TipTap editor with user login, status display, and version management.App.tsx
Complete App.tsx
Editor.tsx
Complete Editor.tsx

How It Works

  1. useCollaboration (React) / createCollaboration (non-React) creates a CollaborationManager. This initializes a CRDT Store (type: 'xml'), a Yjs Y.XmlFragment, and a SyncProvider.
  2. The extension (returned by the hook or manager.createExtension()) bundles Collaboration (Yjs document binding) and CollaborationCaret (remote cursor rendering) as a single TipTap extension.
  3. User types -> TipTap/ProseMirror transaction -> Y.XmlFragment mutation -> Yjs CRDT broadcasts via Velt backend -> all connected clients see the change.
  4. Remote cursors are tracked via Yjs Awareness. The extension renders colored cursor labels at each remote user’s selection position.
  5. Initial content is applied only once for brand-new documents. The manager waits for remote content before deciding the document is new.
  6. Conflict resolution is handled by Yjs CRDTs, concurrent typing at different positions merges correctly, and formatting changes on different text ranges are independent.
  7. Version management saves the full Yjs state as a named snapshot. Restoring a version replaces the current state and broadcasts to all clients.
  8. Cleanup is automatic, the manager destroys the editor bindings, store, and all listeners when the editor is destroyed or the component unmounts.

APIs

React: useCollaboration()

The primary React hook for collaborative TipTap editing. Creates a CollaborationManager, initializes the CRDT store, and returns a TipTap Extension with cursor support.
  • Signature: useCollaboration(config: UseCollaborationConfig)
  • Params: UseCollaborationConfig
    • editorId: Unique identifier for this collaborative session.
    • initialContent: HTML content applied once for brand-new documents. Default: empty paragraph.
    • debounceMs: Throttle interval (ms) for backend writes. Default: 0.
    • forceResetInitialContent: If true, always clear and re-apply initialContent. Default: false.
    • veltClient: Explicit Velt client. Falls back to VeltProvider context.
    • onError: Error callback.
  • Returns: UseCollaborationReturn
    • extension: TipTap Extension bundling Yjs binding + cursor rendering. null while loading.
    • isLoading: true until CollaborationManager is initialized.
    • isSynced: true after the initial sync with the backend completes.
    • status: Connection status: 'connecting', 'connected', or 'disconnected'.
    • error: Most recent error, or null.
    • manager: The underlying CollaborationManager. null before initialization.

Non-React: createCollaboration()

Factory function that creates a CollaborationManager, calls initialize(), and returns a ready-to-use instance.
  • Signature: createCollaboration(config: CollaborationConfig): Promise<CollaborationManager>
  • Params: CollaborationConfig
    • editorId: Unique editor/document identifier for syncing.
    • veltClient: Velt client instance must have an authenticated user.
    • initialContent: HTML content applied once for brand-new documents.
    • debounceMs: Throttle interval (ms) for backend writes. Default: 0.
    • onError: Callback for non-fatal errors.
    • forceResetInitialContent: If true, always reset to initialContent. Default: false.
  • Returns: Promise<CollaborationManager>

CollaborationManager Methods

Once the manager is available (non-null from the hook, or returned from createCollaboration), you can use its full API:

manager.createExtension()

Returns a single TipTap Extension that bundles Yjs document binding and remote cursor rendering. Non-React only, the React hook returns the extension directly.
  • Returns: Extension

manager.onStatusChange()

Subscribe to connection status changes.
  • Signature: manager.onStatusChange(callback: (status: SyncStatus) => void): Unsubscribe
  • Returns: Unsubscribe (call to stop listening)

manager.onSynced()

Subscribe to sync state changes.
  • Signature: manager.onSynced(callback: (synced: boolean) => void): Unsubscribe
  • Returns: Unsubscribe

manager.initialized

Whether initialize() has completed.
  • Returns: boolean

manager.synced

Whether initial sync with the backend has completed.
  • Returns: boolean

manager.status

Current connection status.
  • Returns: SyncStatus ('connecting' | 'connected' | 'disconnected')

manager.saveVersion()

Save a named snapshot. Returns the version ID.
  • Signature: manager.saveVersion(name: string): Promise<string>
  • Returns: Promise<string>

manager.getVersions()

List all saved versions.
  • Returns: Promise<Version[]>

manager.restoreVersion()

Restore to a saved version. Returns true on success. The restored state is pushed to all connected clients.
  • Signature: manager.restoreVersion(versionId: string): Promise<boolean>
  • Returns: Promise<boolean>

manager.setStateFromVersion()

Apply a Version object’s state locally to the current document.
  • Signature: manager.setStateFromVersion(version: Version): Promise<void>
  • Returns: Promise<void>

manager.getDoc()

Get the underlying Yjs document.
  • Returns: Y.Doc

manager.getXmlFragment()

Get the XmlFragment bound to TipTap content.
  • Returns: Y.XmlFragment | null

manager.getProvider()

Get the sync provider.
  • Returns: SyncProvider

manager.getAwareness()

Get the Yjs Awareness instance.
  • Returns: Awareness

manager.getStore()

Get the core CRDT Store.
  • Returns: Store<string>

manager.destroy()

Full cleanup (automatic on editor destroy or component unmount). Safe to call multiple times.
  • Returns: void

Custom Encryption

Encrypt CRDT data before it’s stored in Velt by registering a custom encryption provider. For CRDT methods, input data is of type Uint8Array | number[].
See also: setEncryptionProvider() · VeltEncryptionProvider · EncryptConfig · DecryptConfig

Migration Guide: v1 to v2

React

Overview

The v2 API replaces useVeltTiptapCrdtExtension() with useCollaboration(). The new hook provides richer reactive state (status, sync, error) and exposes the CollaborationManager for advanced use.

Key Changes

Step-by-Step

1. Replace the hook:
2. Replace extension usage:
3. Replace version management:
4. Add status monitoring (new in v2):

Non-React

Overview

The v2 API replaces the callback-based createVeltTiptapCrdtExtension() with an async createCollaboration() factory that returns a CollaborationManager.

Key Changes

Step-by-Step

1. Replace the extension creation:
2. Replace version management:
3. Replace cleanup:
4. Add status monitoring (new in v2):
5. Access Yjs internals:

Legacy API (v1)

The v1 API is deprecated. Use the v2 useCollaboration hook (React) or createCollaboration (non-React) for all new integrations. The v1 API is still exported for backward compatibility.

React: useVeltTiptapCrdtExtension() (deprecated)

A React hook that returns a TipTap extension and store. Internally delegates to useCollaboration (v2) via a compatibility wrapper.

React: VeltTiptapCrdtExtensionConfig (deprecated)

React: VeltTiptapCrdtExtensionResponse (deprecated)

Non-React: createVeltTiptapCrdtExtension() (deprecated)

Creates a collaboration extension using a callback-based pattern. The function subscribes to the Velt SDK lifecycle and invokes the onReady callback when the extension and store are ready.
  • Signature: createVeltTiptapCrdtExtension(config: VeltTipTapStoreConfig, onReady: (response: VeltTiptapCrdtExtensionResponse) => void): () => void
  • Returns: () => void (cleanup function)

Non-React: VeltTipTapStoreConfig (deprecated)

Non-React: VeltTiptapCrdtExtensionResponse (deprecated)

VeltTipTapStore Methods (deprecated)