@veltdev/prosemirror-crdt-react and @veltdev/prosemirror-crdt libraries enable real-time collaborative editing in ProseMirror. The collaboration engine is built on Yjs, y-prosemirror, and the Velt SDK.
Prerequisites
- Node.js (v14 or higher)
- A Velt account with an API key (sign up)
- React (v18 or higher) when using the React wrapper
- Optional: TypeScript for type safety
Setup
Step 1: Install Dependencies
- React / Next.js
- Other Frameworks
yjs and y-prosemirror in your application. Duplicate Yjs constructors can break plugin bindings and awareness.
Step 2: Setup Velt
Initialize Velt, authenticate a user, and set a stable document context before collaboration starts. Follow the Velt Setup Docs for the production token endpoint used byauthProvider.
- React / Next.js
- Other Frameworks
Step 3: Create a Schema
All collaborators must use a compatible ProseMirror schema. The React package includes a default schema with basic nodes, list nodes, and a highlight mark:- React / Next.js
- Other Frameworks
- React / Next.js
- Other Frameworks
Step 4: Initialize Collaborative Editor
- React Component
- React Hook
- Other Frameworks
ProseMirrorCrdtEditor creates the EditorView, installs the Yjs plugins, and manages cleanup.schema is omitted, the component uses defaultProseMirrorSchema.Step 5: Add Plugins, Keymaps, and CRDT Undo
Extra plugins are appended after the Yjs sync, cursor, and undo plugins:- React / Next.js
- Other Frameworks
keymap prop; the manager combines them with Yjs-aware undo and redo.Step 6: Bring Your Own EditorView (Optional)
- React / Next.js
- Other Frameworks
Pass an existing The hook attaches the view to the manager but does not rebuild an existing state’s plugin list. Ensure the view was created with
EditorView to the hook when your application owns its lifecycle:manager.createCollaborationPlugins() or use manager.createEditorState() / manager.createEditorView().Step 7: Style Remote Cursors
[data-remote-cursor], [data-remote-cursor-label], and [data-remote-selection] attributes. Use these stable hooks for custom styling or automated verification when class names are not sufficient.
Pass disableCursors to keep document synchronization enabled without installing the cursor plugin:
- React / Next.js
- Other Frameworks
Step 8: Status Monitoring (Optional)
- React / Next.js
- Other Frameworks
onStatusChange, and the hook exposes reactive status, synced / isSynced, and error state.Step 9: Version Management (Optional)
- React / Next.js
- Other Frameworks
Y.XmlFragment for all collaborators. Refresh version metadata after peer version activity.
Version metadata is not part of the shared ProseMirror fragment. If every collaborator displays a version list, broadcast a lightweight awareness field after a successful save or restore and refresh when a peer changes it:
broadcastVersionRefresh(currentUserId) after saveVersion() or restoreVersion(). Awareness is only an invalidation signal; always re-fetch the authoritative version list.
Step 10: Force Reset Initial Content (Optional)
Seed content can be plain text or ProseMirror JSON:- React / Next.js
- Other Frameworks
forceResetInitialContent only for a deliberate reset. The supplied content is parsed using the configured schema.
Step 11: CRDT Event Subscription (Optional)
- React / Next.js
- Other Frameworks
Step 12: Custom Encryption (Optional)
- React / Next.js
- Other Frameworks
Step 13: Error Handling (Optional)
- React / Next.js
- Other Frameworks
Step 14: Access Yjs Internals (Advanced)
- React / Next.js
- Other Frameworks
Step 15: Enable, Disable, and Cleanup
- React / Next.js
- Other Frameworks
EditorView it created. It does not destroy a consumer-owned view unless you explicitly opt into that lifecycle.Notes
- Stable schema: Construct one schema and keep node and mark names compatible across clients.
- Plugin order: Yjs sync, cursor, and undo plugins are installed before consumer plugins.
- CRDT history: Import the Yjs-aware
undoandredocommands from the Velt ProseMirror package used by your integration; do not addprosemirror-history. - Shared data: Rich document structure is stored in a
Y.XmlFragment, not serialized HTML. - Shared content key: The manager stores that fragment under the
prosemirrorkey. - Unique editorId: Collaborators must share both the Velt document context and
editorId. - Initial content: Plain text or ProseMirror JSON is applied once to a new shared fragment.
- View ownership: The hook can create an
EditorViewor attach one owned by the application. - Direct lifecycle: Create the manager with
autoInitialize: false, create plugins, create state, create the view, attach it, and callinitialize()last. - Other Frameworks lifecycle: Unsubscribe callbacks, destroy the
EditorView, and then destroy the manager. - Production authentication: Use Velt’s recommended server-backed authentication flow.
Testing and Debugging
To test collaboration:- Log in as two unique users in separate browser profiles.
- Open the same Velt document and ProseMirror
editorId. - Edit text, marks, lists, and selections concurrently.
- Verify Yjs undo affects only the local user’s changes and shared content persists after reload.
- Unknown node or mark: Ensure all clients use a compatible schema and migrated JSON matches it.
- Remote updates missing: Create the collaboration plugins and view before calling
manager.initialize(). - Undo removes peer changes: Remove
prosemirror-historyand use the exported Yjs commands. - Cursors not appearing: Add cursor CSS and ensure
disableCursorsis false. - Duplicate Yjs warning: Deduplicate
yjsandy-prosemirror.
Complete Example
- React / Next.js
- Other Frameworks
Complete App.tsx
Complete prosemirror.css
How It Works
- React applications initialize Velt with an authenticated
VeltProvider; other frameworks useinitVelt(). Both authenticate before setting the document. - The React wrapper waits for Velt, an authenticated user, and a mount or existing view. It uses the supplied schema when present and otherwise uses the package’s default schema.
- Direct integrations create a non-initialized manager, install its Yjs sync, cursor, and undo plugins in an
EditorState, create and attach theEditorView, and only then initialize the shared store. y-prosemirrorbinds ProseMirror transactions to a sharedY.XmlFragment, while awareness carries user identity and selections.- Yjs merges concurrent structural edits, and the shared schema validates the resulting ProseMirror document.
- Velt persists updates and named versions.
APIs
React: ProseMirrorCrdtEditor
*Provide either
editorId or documentId.
React: useCollaboration()
The hook additionally acceptsmount, editorView, state, and destroyViewOnUnmount.
Schema and Command Helpers
createDefaultProseMirrorSchema()creates the default basic/list/highlight schema.defaultProseMirrorSchemais a pre-created default schema.createHighlightMarkSpec(defaultColor?)creates the included highlight mark spec.undoandredoare Yjs-aware commands re-exported by both@veltdev/prosemirror-crdt-reactand@veltdev/prosemirror-crdt. Import them from the Velt package used by your integration.
Other Frameworks: createCollaboration()
After this call, follow the exact plugin, state, view, attach, and initialize sequence shown in Step 4.
CollaborationManager Methods
Exported Types
ProseMirrorPluginOptions,ProseMirrorEditorViewOptionsUseCollaborationConfig/UseProseMirrorCrdtConfigUseCollaborationReturn/ProseMirrorCrdtHookResultProseMirrorCrdtEditorProps,ProseMirrorJSON,CursorDataSyncStatus,Unsubscribe,Version
CollaborationConfig type from @veltdev/prosemirror-crdt.
