@veltdev/blocknote-crdt-react and @veltdev/blocknote-crdt libraries enable real-time collaborative editing on BlockNote 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 BlockNote editor.
- Pass an
editorIdto 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 a BlockNoteCollaborationConfig. You pass this config to useCreateBlockNote({ collaboration: ... }) when creating your BlockNote editor.When the
collaboration config is provided, BlockNote automatically uses the Y.XmlFragment as the document source, enables remote cursor rendering, and switches undo/redo to Yjs UndoManager. No additional configuration is needed.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:Step 5: Version Management (Optional)
Save and restore named snapshots of the document state.- React / Next.js
- Other Frameworks
Version methods are returned directly from
useCollaboration as first-class APIs: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 usinggetCrdtElement().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 typeUint8Array | number[].
- React / Next.js
- Other Frameworks
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.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 callingmanager.destroy(), which cascades to the store, provider, and all listeners. Safe to call multiple times.
- React / Next.js
- Other Frameworks
Notes
- Unique editorId: Use a unique
editorIdper editor instance. - Pass collaborationConfig: Provide the
collaborationconfig fromuseCollaboration(React) ormanager.getCollaborationConfig()(non-React) to the BlockNote editor. - Undo/redo: Automatically switches to Yjs UndoManager when collaboration is enabled.
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.
- Cursors not appearing: Ensure each editor has a unique
editorIdand 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.
Complete Example
- React / Next.js
- Other Frameworks
- Live Demo
A complete collaborative BlockNote editor with user login, status display, and version management.App.tsxEditor.tsx
Complete App.tsx
Complete Editor.tsx
How It Works
useCollaboration(React) /createCollaboration(non-React) creates aCollaborationManager. This initializes a CRDT Store (type: 'xml', content key'document-store'), a YjsY.XmlFragment, and aSyncProvider.- The collaboration config (returned by the hook or
manager.getCollaborationConfig()) contains theSyncProvider,Y.XmlFragment, user info, and cursor label settings. This config is passed touseCreateBlockNote({ collaboration: ... })(React) orBlockNoteEditor.create({ collaboration: ... })(non-React). - User types -> BlockNote/ProseMirror transaction ->
Y.XmlFragmentmutation -> Yjs CRDT broadcasts via Velt backend -> all connected clients see the change. - Remote cursors are tracked via Yjs Awareness. BlockNote renders colored cursor labels at each remote user’s selection position using its built-in collaboration cursor support.
- Undo/redo automatically switches to Yjs UndoManager when collaboration is enabled — no need to disable any history extension.
- 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, and formatting changes on different text ranges are independent.
- 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 store, provider, and all listeners when destroyed or the component unmounts.
APIs
React: useCollaboration()
The primary React hook for collaborative BlockNote editing. Creates aCollaborationManager, initializes the CRDT store, and returns a BlockNoteCollaborationConfig with cursor support.
- Signature:
useCollaboration(config: UseCollaborationConfig) - Params: UseCollaborationConfig
editorId: Unique identifier for this collaborative session.initialContent: Block content applied once for brand-new documents.debounceMs: Throttle interval (ms) for backend writes. Default:0.forceResetInitialContent: Iftrue, always clear and re-applyinitialContent. Default:false.veltClient: Explicit Velt client. Falls back toVeltProvidercontext.showCursorLabels: Cursor label display mode. Default:'activity'.onError: Error callback.
- Returns: UseCollaborationReturn
collaborationConfig: BlockNote collaboration config.nullwhile loading.isLoading:trueuntil CollaborationManager is initialized.isSynced:trueafter the initial sync with the backend completes.status: Connection status:'connecting','connected', or'disconnected'.error: Most recent error, ornull.manager: The underlyingCollaborationManager.nullbefore initialization.saveVersion: Save a named version snapshot. Returns the version ID, or empty string on failure.getVersions: List all saved versions. Returns empty array on failure.restoreVersion: Restore to a saved version. Returnstrueon success,falseon failure.
Non-React: createCollaboration()
Factory function that creates aCollaborationManager, 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: Block content applied once for brand-new documents.debounceMs: Throttle interval (ms) for backend writes. Default:0.onError: Callback for non-fatal errors.forceResetInitialContent: Iftrue, always reset toinitialContent. Default:false.
- Returns:
Promise<CollaborationManager>
CollaborationManager Methods
Once themanager is available (non-null from the hook, or returned from createCollaboration), you can use its full API:
manager.getCollaborationConfig()
Returns the collaboration config object that BlockNote expects. Pass this toBlockNoteEditor.create({ collaboration: ... }) or useCreateBlockNote({ collaboration: ... }).
- Params:
options?: { showCursorLabels?: 'activity' | 'always' }— Optional cursor display overrides - Returns:
BlockNoteCollaborationConfig | null
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
Whetherinitialize() 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 of the current document state.- Signature:
manager.saveVersion(name: string): Promise<string> - Returns:
Promise<string>(version ID, or empty string on failure)
manager.getVersions()
List all saved versions for this document.- Returns:
Promise<Version[]>(empty array on failure)
manager.restoreVersion()
Restore the document to a previously saved version. The restored state is pushed to all connected clients.- Signature:
manager.restoreVersion(versionId: string): Promise<boolean> - Returns:
Promise<boolean>(trueon success,falseon failure)
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 | null
manager.getXmlFragment()
Get the XmlFragment bound to BlockNote’sdocument-store key.
- Returns:
Y.XmlFragment | null
manager.getProvider()
Get the sync provider.- Returns:
SyncProvider | null
manager.getAwareness()
Get the Yjs Awareness instance.- Returns:
Awareness | null
manager.getStore()
Get the core CRDT Store.- Returns:
Store<string> | null
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 typeUint8Array | number[].
- React / Next.js
- Other Frameworks
Migration Guide: v1 to v2
React
Overview
The v2 API replacesuseVeltBlockNoteCrdtExtension() with useCollaboration(). The new hook provides richer reactive state (status, sync, error), returns version management methods directly, and exposes the CollaborationManager for advanced use.
Key Changes
Step-by-Step
1. Replace the hook:Non-React
Overview
The non-React v2 API providescreateCollaboration() from @veltdev/blocknote-crdt, replacing any previous callback-based patterns. The returned CollaborationManager provides getCollaborationConfig() for BlockNote editor creation.
Step-by-Step
1. Create the manager:Legacy API (v1)
React: useVeltBlockNoteCrdtExtension() (deprecated)
A React hook that returns a collaboration config and store for BlockNote integration. Internally delegates touseCollaboration (v2) via a compatibility wrapper.
- Signature:
useVeltBlockNoteCrdtExtension(config: VeltBlockNoteCrdtExtensionConfig) - Params: VeltBlockNoteCrdtExtensionConfig
- Returns: VeltBlockNoteCrdtExtensionResponse

