The @veltdev/codemirror-crdt-react library enables real-time collaborative editing on CodeMirror 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/codemirror-crdt-react @veltdev/react

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 CodeMirror 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.
// Initialize the Velt CRDT extension
const { store, isLoading } = useVeltCodeMirrorCrdtExtension({ editorId });

useEffect(() => {
  if (!store || !editorRef.current) return;

  const startState = EditorState.create({
    doc: store.getYText()?.toString() ?? '',
    extensions: [
      basicSetup,
      yCollab(store.getYText()!, store.getAwareness(), { undoManager: store.getUndoManager() }),
    ],
  });

  viewRef.current = new EditorView({
      state: startState,
      parent: editorRef.current,
  });

  return () => viewRef.current?.destroy();
}, [store]);

Notes

  • Unique editorId: Use a unique editorId per editor instance.
  • Wait for auth: Initialize the store only after the Velt client is ready.
  • Use yCollab: Pass the store’s Yjs text, awareness, and undo manager to yCollab.
  • Initialize and clean up: Destroy the EditorView and store on unmount to avoid leaks.

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.
  • Disconnected session: Check network.

Complete Example

See Velt CodeMirror Demo Repo for our complete implementation.
Relevant Code
// Import required modules
import { useVeltCodeMirrorCrdtExtension } from "@veltdev/codemirror-crdt-react";
import { yCollab } from "y-codemirror.next";
import { EditorState } from "@codemirror/state";
import { basicSetup, EditorView } from "codemirror";

// Add hook in your codemirror editor component
const { store, isLoading } = useVeltCodeMirrorCrdtExtension({ editorId });

useEffect(() => {
    if (!store || !editorRef.current) return;

    // Clean up existing view if any
    viewRef.current?.destroy();

    const startState = EditorState.create({
        doc: store.getYText()?.toString() ?? '',
        extensions: [
            basicSetup,
            autocompletion(),
            yCollab(store.getYText()!, store.getAwareness(), { undoManager: store.getUndoManager() }),
        ],
    });

    viewRef.current = new EditorView({
        state: startState,
        parent: editorRef.current,
    });

    return () => {
        viewRef.current?.destroy();
        viewRef.current = null;
    };
}, [store]);

APIs

Custom Encryption

You can 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

useVeltCodeMirrorCrdtExtension()

Provides real-time collaborative editing on CodeMirror editor with Yjs and Velt SDK.
  • Signature: useVeltCodeMirrorCrdtExtension(config: VeltCodeMirrorCrdtExtensionConfig)
  • Params: VeltCodeMirrorCrdtExtensionConfig
    • editorId: Unique identifier for the editor instance.
    • initialContent: Initial content for the editor.
    • debounceMs: Debounce time for update propagation (ms).
  • Returns: VeltCodeMirrorCrdtExtensionResponse
    • store: CodeMirror CRDT store instance.
    • isLoading: Whether the store is initialized. false once store is initialized, true by default.
  • React Hook: useVeltCodeMirrorCrdtExtension()

Store Methods

store.getStore()

Get the underlying Store<string> that manages document state and synchronization.
  • Returns: Store<string>
const underlying = store.getStore();

store.getYDoc()

Access the Yjs document used for collaborative editing.
  • Returns: Y.Doc
const ydoc = store.getYDoc();

store.getYText()

Get the shared Y.Text bound to the current CodeMirror document.
  • Returns: Y.Text | null
const docText = store.getYText()?.toString() ?? '';

store.getAwareness()

Access the Yjs Awareness instance associated with the store.
  • Returns: Awareness
yCollab(store.getYText()!, store.getAwareness(), { undoManager: store.getUndoManager() });

store.getUndoManager()

Access the Yjs UndoManager for collaborative undo/redo.
  • Returns: Y.UndoManager
const extensions = [
  basicSetup,
  yCollab(store.getYText()!, store.getAwareness(), { undoManager: store.getUndoManager() }),
];