The @veltdev/tiptap-crdt-react library enables 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:
npm install @veltdev/tiptap-crdt-react @tiptap/react @tiptap/starter-kit @tiptap/extension-collaboration @tiptap/extension-collaboration-cursor

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 Velt CRDT Extension

  • Initialize the Velt CRDT extension and use it to initialize 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.
const { VeltCrdt } = useVeltTiptapCrdtExtension({
  editorId: 'YOUR_EDITOR_ID'
});

const editor = useEditor({
  extensions: [
    StarterKit.configure({
      history: false,
    }),
    ...(VeltCrdt ? [VeltCrdt] : []),
  ],
  content: ''
}, [VeltCrdt]);

return <EditorContent editor={editor} />;

Notes

  • Unique editorId: Use a unique editorId per editor instance.
  • Disable history: Turn off Tiptap history 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 is disabled when using collaboration.

Complete Example

See Velt Tiptap Demo Repo for our complete implementation.
Relevant Code
    import { EditorContent, useEditor } from '@tiptap/react';
    import StarterKit from '@tiptap/starter-kit';
    import { useVeltTiptapCrdtExtension } from '@veltdev/tiptap-crdt-react';
    import React from 'react';

    const CollaborativeEditor: React.FC = () => {

      const { VeltCrdt } = useVeltTiptapCrdtExtension({
        editorId: 'YOUR_EDITOR_ID'
      });
      // Initialize the editor with our collaboration extension
      const editor = useEditor({
        extensions: [
          StarterKit.configure({
            history: false,
          }),
          ...(VeltCrdt ? [VeltCrdt] : []),
        ],
        content: ''
      }, [VeltCrdt]);

      return (
        <div className="editor-container">
          <div className="editor-content">
            <EditorContent editor={editor} />
          </div>
          <div className="status">
            {VeltCrdt ? 'Connected to collaborative session' : 'Connecting to collaborative session...'}
          </div>
        </div>
      );
    };
    export default CollaborativeEditor;

APIs

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[].
async function encryptData(config: EncryptConfig<number[]>): Promise<string> {
  const encryptedData = await yourEncryptDataMethod(config.data);
  return encryptedData;
}

async function decryptData(config: DecryptConfig<string>): Promise<number[]> {
  const decryptedData = await yourDecryptDataMethod(config.data);
  return decryptedData;
}

const encryptionProvider: VeltEncryptionProvider<number[], string> = {
  encrypt: encryptData,
  decrypt: decryptData,
};

<VeltProvider
  apiKey="YOUR_API_KEY"
  encryptionProvider={encryptionProvider}
/>
See also: setEncryptionProvider() · VeltEncryptionProvider · EncryptConfig · DecryptConfig

useVeltTiptapCrdtExtension()

Provides real-time collaborative editing on Tiptap editor with Yjs and Velt SDK.
  • Signature: useVeltTiptapCrdtExtension(config: VeltTiptapCrdtExtensionConfig)
  • Params: VeltTiptapCrdtExtensionConfig
    • editorId: Unique identifier for the editor instance.
    • initialContent: Initial content for the editor.
    • debounceMs: Debounce time for update propagation (ms).
  • Returns: VeltTiptapCrdtExtensionResponse
    • VeltCrdt: Tiptap Velt CRDT Extension.
    • store: Tiptap CRDT store instance.
    • isLoading: Whether the store is initialized. false once store is initialized, true by default.
const { VeltCrdt, store, isLoading } = useVeltTiptapCrdtExtension({ editorId: 'my-doc' });

Store Methods

store.getStore()

Access the underlying CRDT store managing document state.
  • Returns: Store
const crdtStore = store.getStore();

store.destroy()

Tear down the store and clean up listeners/resources.
  • Returns: void
store.destroy();

store.getYDoc()

Accessor for the underlying Yjs document.
  • Returns: Y.Doc
const ydoc = store.getYDoc();

store.getYXml()

Returns the Y.XmlFragment instance that handles Tiptap’s rich text structure.
  • Returns: Y.XmlFragment | null
const yxml = store.getYXml();