Skip to main content
The @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

Resolve one copy of 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 by authProvider.

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:
Create the schema once at module scope or memoize it. Do not construct a new schema on every render. For a custom highlight mark:
Schema node and mark names are part of the shared document contract. Deploy schema changes as explicit migrations, especially when older clients may still be connected.

Step 4: Initialize Collaborative Editor

ProseMirrorCrdtEditor creates the EditorView, installs the Yjs plugins, and manages cleanup.
If 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:
You can instead pass commands through the keymap prop; the manager combines them with Yjs-aware undo and redo.
Import Yjs-aware undo and redo from @veltdev/prosemirror-crdt-react or @veltdev/prosemirror-crdt, according to your integration. Do not import them directly from y-prosemirror in application code, and do not add prosemirror-history because it creates a separate local history that does not understand remote CRDT transactions.

Step 6: Bring Your Own EditorView (Optional)

Pass an existing EditorView to the hook when your application owns its lifecycle:
The hook attaches the view to the manager but does not rebuild an existing state’s plugin list. Ensure the view was created with manager.createCollaborationPlugins() or use manager.createEditorState() / manager.createEditorView().

Step 7: Style Remote Cursors

The renderer also exposes [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:

Step 8: Status Monitoring (Optional)

The component also accepts onStatusChange, and the hook exposes reactive status, synced / isSynced, and error state.

Step 9: Version Management (Optional)

Restoring a version changes the shared 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:
Call 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:
Set forceResetInitialContent only for a deliberate reset. The supplied content is parsed using the configured schema.
Invalid JSON nodes or marks are rejected by the schema. Validate migrated content before using it as initialContent.

Step 11: CRDT Event Subscription (Optional)

Step 12: Custom Encryption (Optional)

See also: setEncryptionProvider() · VeltEncryptionProvider · EncryptConfig · DecryptConfig

Step 13: Error Handling (Optional)

Step 14: Access Yjs Internals (Advanced)

Step 15: Enable, Disable, and Cleanup

The component and hook destroy the manager automatically. By default, the hook also destroys an 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 undo and redo commands from the Velt ProseMirror package used by your integration; do not add prosemirror-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 prosemirror key.
  • 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 EditorView or attach one owned by the application.
  • Direct lifecycle: Create the manager with autoInitialize: false, create plugins, create state, create the view, attach it, and call initialize() 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:
  1. Log in as two unique users in separate browser profiles.
  2. Open the same Velt document and ProseMirror editorId.
  3. Edit text, marks, lists, and selections concurrently.
  4. Verify Yjs undo affects only the local user’s changes and shared content persists after reload.
Common issues:
  • 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-history and use the exported Yjs commands.
  • Cursors not appearing: Add cursor CSS and ensure disableCursors is false.
  • Duplicate Yjs warning: Deduplicate yjs and y-prosemirror.
Use the VeltCrdtStoreMap debugging interface to inspect the shared XML fragment and provider state.

Complete Example

Complete App.tsx
Complete prosemirror.css

How It Works

  1. React applications initialize Velt with an authenticated VeltProvider; other frameworks use initVelt(). Both authenticate before setting the document.
  2. 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.
  3. Direct integrations create a non-initialized manager, install its Yjs sync, cursor, and undo plugins in an EditorState, create and attach the EditorView, and only then initialize the shared store.
  4. y-prosemirror binds ProseMirror transactions to a shared Y.XmlFragment, while awareness carries user identity and selections.
  5. Yjs merges concurrent structural edits, and the shared schema validates the resulting ProseMirror document.
  6. Velt persists updates and named versions.

APIs

React: ProseMirrorCrdtEditor

*Provide either editorId or documentId.

React: useCollaboration()

The hook additionally accepts mount, editorView, state, and destroyViewOnUnmount.

Schema and Command Helpers

  • createDefaultProseMirrorSchema() creates the default basic/list/highlight schema.
  • defaultProseMirrorSchema is a pre-created default schema.
  • createHighlightMarkSpec(defaultColor?) creates the included highlight mark spec.
  • undo and redo are Yjs-aware commands re-exported by both @veltdev/prosemirror-crdt-react and @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, ProseMirrorEditorViewOptions
  • UseCollaborationConfig / UseProseMirrorCrdtConfig
  • UseCollaborationReturn / ProseMirrorCrdtHookResult
  • ProseMirrorCrdtEditorProps, ProseMirrorJSON, CursorData
  • SyncStatus, Unsubscribe, Version
Import the imperative CollaborationConfig type from @veltdev/prosemirror-crdt.