The @veltdev/codemirror-crdt-react and @veltdev/codemirror-crdt libraries enable 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:
React / Next.js
Other Frameworks
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 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.
React / Next.js
Other Frameworks
Use the useCollaboration hook to manage the entire CRDT lifecycle. It creates a CollaborationManager, initializes the CRDT store, and returns collaboration primitives (ytext, awareness, undoManager, doc) that you pass to yCollab() from y-codemirror.next. Use createCollaboration() to create a CollaborationManager, then call manager.getCollaborationPrimitives() to get the Yjs primitives (ytext, awareness, undoManager) and pass them to yCollab() from y-codemirror.next.
Step 4: Status Monitoring (Optional)
Monitor the connection status and sync state to display UI indicators.
React / Next.js
Other Frameworks
The useCollaboration hook returns reactive status, isSynced, and error state: Subscribe to status and sync changes on the manager:
Step 5: Version Management (Optional)
Save and restore named snapshots of the document state.
React / Next.js
Other Frameworks
The manager returned by useCollaboration provides version management methods:
Step 6: 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.
React / Next.js
Other Frameworks
Step 7: CRDT Event Subscription (Optional)
Listen for real-time sync events using getCrdtElement().on() from the Velt client.
React / Next.js
Other Frameworks
Step 8: 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[].
React / Next.js
Other Frameworks
See also: setEncryptionProvider()
· VeltEncryptionProvider
· EncryptConfig
· DecryptConfig
Step 9: Error Handling (Optional)
React / Next.js
Other Frameworks
Pass an onError callback to handle initialization or runtime errors. The error reactive state is also available for rendering. Pass an onError callback when creating the CollaborationManager:
Step 10: Access Yjs Internals (Advanced)
The manager exposes escape hatches for direct Yjs manipulation when needed.
React / Next.js
Other Frameworks
Step 11: Cleanup
Cleanup is handled by calling manager.destroy(), which cascades to the store, provider, undo manager, and all listeners. Safe to call multiple times.
React / Next.js
Other Frameworks
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 Yjs text, awareness, and undo manager to
yCollab.
- Initialize and clean up: Destroy the
EditorView and manager on unmount to avoid leaks.
Testing and Debugging
To test collaboration:
- Login two unique users on two browser profiles and open the same page in both.
- 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
A complete collaborative CodeMirror editor with user login, status display, and version management.App.tsxEditor.tsx A complete collaborative CodeMirror editor with SDK initialization, user login, status display, and version management.velt.tseditor.tsHTML structuremain.ts
How It Works
useCollaboration (React) / createCollaboration (non-React) creates a CollaborationManager. This initializes a CRDT Store (type: 'text'), a Yjs Y.Text, and a SyncProvider.
- The primitives (
ytext, awareness, undoManager, doc) are returned by the hook or via manager.getCollaborationPrimitives(). You pass these to yCollab() from y-codemirror.next as a CodeMirror extension.
- User types -> CodeMirror transaction ->
Y.Text mutation -> Yjs CRDT broadcasts via Velt backend -> all connected clients see the change.
- Remote cursors are tracked via Yjs Awareness. The
yCollab extension renders colored cursor indicators at each remote user’s selection position.
- Initial content is applied only once for brand-new documents. The manager waits for remote content before deciding the document is new.
- Conflict resolution is handled by Yjs CRDTs, concurrent typing at different positions merges correctly.
- Version management saves the full Yjs state as a named snapshot. Restoring a version replaces the current state and broadcasts to all clients.
- Cleanup is automatic, the manager destroys the editor bindings, store, and all listeners when destroyed or the component unmounts.
APIs
React: useCollaboration()
The primary React hook for collaborative CodeMirror editing. Creates a CollaborationManager, initializes the CRDT store, and returns collaboration primitives for CodeMirror integration.
- Signature:
useCollaboration(config: UseCollaborationConfig)
- Params: UseCollaborationConfig
editorId: Unique identifier for this collaborative session.
initialContent: Text content applied once for brand-new documents.
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
primitives: Yjs primitives for CodeMirror. 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: Plain text 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>
CollaborationPrimitives
The primitives object (from the React hook) or manager.getCollaborationPrimitives() (non-React) contains everything you need to set up yCollab():
| Property | Type | Description |
|---|
ytext | Y.Text | null | Y.Text bound to the document. Pass to yCollab(). |
awareness | Awareness | Awareness instance for cursor/presence tracking. |
undoManager | Y.UndoManager | null | Collaborative undo manager (tracks local changes only). |
doc | Y.Doc | Underlying Yjs document. |
CollaborationManager Methods
Once the manager is available (non-null from the hook, or returned from createCollaboration), you can use its full API:
manager.getCollaborationPrimitives()
Returns the Yjs primitives needed to call yCollab(). Non-React only, the React hook returns primitives directly.
- Returns:
{ ytext: Y.Text | null, awareness: Awareness, undoManager: Y.UndoManager | null, doc: Y.Doc }
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.
manager.synced
Whether initial sync with the backend has completed.
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.
manager.getText() / manager.getYText()
Get the Y.Text bound to the document content. In React, this method is called getText(); in non-React, it is called getYText().
manager.getProvider()
Get the sync provider.
manager.getAwareness()
Get the Yjs Awareness instance.
manager.getStore()
Get the core CRDT Store.
manager.getUndoManager()
Get the Y.UndoManager for undo/redo operations.
- Returns:
Y.UndoManager | null
manager.destroy()
Full cleanup (automatic on editor destroy or component unmount). Safe to call multiple times.
Migration Guide: v1 to v2
React
Overview
The v2 API replaces useVeltCodeMirrorCrdtExtension() with useCollaboration(). The new hook provides richer reactive state (status, sync, error), returns Yjs primitives directly, and exposes the CollaborationManager for advanced use.
Key Changes
| Aspect | v1 (deprecated) | v2 (current) |
|---|
| Entry point | useVeltCodeMirrorCrdtExtension(config) | useCollaboration(config) |
| Yjs access | store.getYText(), store.getAwareness() | primitives.ytext, primitives.awareness |
| Undo manager | store.getUndoManager() | primitives.undoManager |
| Manager access | store (VeltCodeMirrorStore) | manager (CollaborationManager) |
| Version management | store.saveVersion(), store.getVersions() | manager.saveVersion(), manager.getVersions() |
| Status tracking | Not available | status, isSynced (reactive state) |
| Error handling | Not available | onError callback + error state |
| Cleanup | Automatic on unmount | Automatic on unmount |
Step-by-Step
1. Replace the hook:
2. Replace editor creation:
3. Replace version management:
4. Add status monitoring (new in v2):
Non-React
Overview
The v2 API replaces the callback-based createVeltCodeMirrorCrdtExtension() with an async createCollaboration() factory that returns a CollaborationManager.
Key Changes
| Aspect | v1 (deprecated) | v2 (current) |
|---|
| Entry point | createVeltCodeMirrorCrdtExtension(config, callback) | await createCollaboration(config) |
| Return value | Cleanup function | CollaborationManager instance |
| Yjs primitives | Via callback: store.getYText(), store.getAwareness() | Via method: manager.getCollaborationPrimitives() |
| Store access | Via callback: response.store | Via method: manager.getStore() |
| Version management | store.getEncodedState(), store.setStateFromVersion() | manager.saveVersion(), manager.getVersions() |
| Status tracking | Not available | manager.onStatusChange(), manager.onSynced() |
| Cleanup | Call returned cleanup function | manager.destroy() |
| Error handling | onConnectionError callback | onError callback |
| Sync notification | onSynced callback (fires once) | manager.onSynced() (subscribable) |
Step-by-Step
1. Replace the store 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: useVeltCodeMirrorCrdtExtension() (deprecated)
A React hook that returns a store for CodeMirror integration. Internally delegates to useCollaboration (v2) via a compatibility wrapper.
React: VeltCodeMirrorCrdtExtensionConfig (deprecated)
| Property | Type | Required | Description |
|---|
editorId | string | Yes | Unique editor identifier. |
initialContent | string | No | Text content for new documents. |
debounceMs | number | No | Throttle interval (ms). |
veltClient | Velt | No | Velt client instance. |
React: VeltCodeMirrorCrdtExtensionResponse (deprecated)
| Property | Type | Description |
|---|
store | VeltCodeMirrorStore | null | Store instance for editor and version management. |
isLoading | boolean | true until the store is ready. |
Non-React: createVeltCodeMirrorCrdtExtension() (deprecated)
Creates a collaboration store using a callback-based pattern. The function subscribes to the Velt SDK lifecycle and invokes the onReady callback when the store is ready.
- Signature:
createVeltCodeMirrorCrdtExtension(config: VeltCodeMirrorStoreConfig, onReady: (response) => void): () => void
- Returns:
() => void (cleanup function)
Non-React: VeltCodeMirrorStoreConfig (deprecated)
| Property | Type | Required | Description |
|---|
editorId | string | Yes | Unique editor identifier. |
initialContent | string | No | Text content for new documents. |
debounceMs | number | No | Throttle interval (ms). |
onSynced | (doc?: Y.Doc) => void | No | Called once when initial sync completes. |
onConnectionError | (error: Error) => void | No | Called on connection errors. |
veltClient | Velt | No | Velt client instance. |
Non-React: VeltCodeMirrorCrdtExtensionResponse (deprecated)
| Property | Type | Description |
|---|
store | VeltCodeMirrorStore | null | Store instance for Yjs access and versions. |
error | string | null | Error message if initialization failed. |
VeltCodeMirrorStore Methods (deprecated)
| Method | Returns | Description |
|---|
getStore() | Store<string> | Get the core CRDT Store. |
getYText() | Y.Text | null | Get the Yjs Text instance. |
getYDoc() | Y.Doc | Get the Yjs Doc. |
getUndoManager() | Y.UndoManager | null | Get the Yjs UndoManager. |
getAwareness() | Awareness | Get the Awareness instance. |
getEncodedState() | number[] | Get encoded Y.Doc state for versioning. |
isConnected() | boolean | Check if connected to backend. |
setStateFromVersion(v) | Promise<void> | Apply a version’s state. |
destroy() | void | Clean up all resources. |