Skip to main content
The @veltdev/ckeditor-crdt-react and @veltdev/ckeditor-crdt libraries enable real-time collaborative editing in CKEditor 5. The collaboration engine is built on Yjs 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
  • A CKEditor 5 license compatible with your build
  • Optional: TypeScript for type safety

Setup

Step 1: Install Dependencies

The React package provides the drop-in component and hook. The base package provides the imperative manager, provider adapter, and HTML content helpers.

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: Configure CKEditor

Import the editor constructor, the features your build needs, and CKEditor styles:
Use your commercial CKEditor license key instead of GPL when required by your distribution. CKEditor serializes highlights as <mark> elements. This markup persists through realtime sync, force reset, and version restore:

Step 4: Initialize Collaborative Editor

  • Pass an editorId to uniquely identify the shared editor.
  • Do not pass CKEditor React’s data or onReady to the drop-in component; the CRDT wrapper owns content and editor capture.
  • Use initialContent as HTML seed content for a new shared document.
The wrapper registers its Yjs fragment observer before the Store loads remote state. This ordering is required: attaching the observer after Store.initialize() can miss the initial server hydration.
Standard CKEditor React props pass through except data and onReady. Use onEditorReady to receive the captured editor and onCKEditorError for CKEditor watchdog errors.
Set the Velt document context before rendering the editor. The React integration explicitly waits until Velt is initialized, a user is authenticated, the CKEditor instance is ready, and enabled is not false. Document context is a prerequisite, but the hook does not explicitly wait for it.

Step 5: Status Monitoring (Optional)

The hook returns reactive status, synced / isSynced, isLoading, and error state. The component offers onStatusChange and onManagerReady callbacks.
Do not disable local editing simply because the provider is connecting. Local HTML changes synchronize when the connection returns.

Step 6: Version Management (Optional)

The hook keeps version metadata in React state and refreshes it after saves and restores:
The methods are no-throw: when unavailable they resolve to '', false, or []. A restore updates the shared HTML for every collaborator.

Step 7: Force Reset Initial Content (Optional)

initialContent is normalized HTML and is applied once to a new empty shared document:
For an explicit reset:
forceResetInitialContent replaces existing shared HTML. Do not enable it during normal initialization.

Step 8: Configure Remote Cursors (Optional)

Remote carets and selections render in an overlay anchored to CKEditor’s editable surface.
Pass a custom cursorsContainer, or set it to null to disable overlay DOM while retaining awareness data.The hook also returns reactive remoteCursors:
Optional host styling:
The built-in overlay creates .velt-ckeditor-remote-cursor, .velt-ckeditor-remote-cursor-caret, .velt-ckeditor-remote-cursor-label, and .velt-ckeditor-remote-selection elements. Cursor labels also carry the collaborator name in data-velt-cursor-name for selectors, tests, or custom styling.

Step 9: CRDT Event Subscription (Optional)

Step 10: Custom Encryption (Optional)

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

Step 11: Error Handling (Optional)

Use onError for collaboration errors and onCKEditorError for official CKEditor React watchdog errors:

Step 12: Access Yjs Internals (Advanced)

The hook’s primitives object groups lower-level resources:
Read or replace normalized shared HTML with the manager:
The CKEditor source build includes its Undo plugin for the native toolbar. For custom collaboration-aware controls, use the manager’s Yjs UndoManager; do not trigger both history paths from the same action.

Step 13: Enable, Disable, and Cleanup

Changing enabled to false destroys the current manager; changing it back reinitializes collaboration. The component and hook also clean up automatically on unmount or dependency changes, and the hook exposes destroy() for early teardown.If you render CKEditor yourself, the editor remains application-owned. Forward onAfterDestroy to editorRef(null) and destroy CKEditor according to its normal lifecycle.

Notes

  • Uncontrolled content: The CRDT manager owns CKEditor data; do not synchronize it through React state.
  • CKEditor configuration: Include every plugin used by the toolbar and provide the correct CKEditor license key.
  • Unique editorId: Collaborators must share the same Velt document context and editor ID.
  • HTML data model: The adapter stores normalized HTML in a Yjs XML fragment and preserves local selection while applying remote content.
  • Initial remote hydration: The manager registers its Yjs fragment observer before Store.initialize(). Preserve that ordering in custom integrations so server state loaded during initialization is not missed.
  • Remote cursors: The adapter renders an overlay and also exposes normalized cursor state for custom UI.
  • StrictMode-safe: The hook destroys stale managers during React remounts and dependency changes.
  • React readiness: The hook waits for Velt initialization, an authenticated user, the CKEditor instance, and enabled. It does not explicitly wait for document context, so set the document first.
  • Editor-first direct setup: Other frameworks create CKEditor before creating the collaboration manager.
  • Other Frameworks lifecycle: Unsubscribe callbacks, destroy the manager, and then destroy the application-owned CKEditor instance.
  • One dependency instance: Deduplicate ckeditor5, yjs, y-protocols, and lib0 in linked or monorepo builds.
  • Initial content: Seed HTML is applied once unless an explicit reset is requested.
  • 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 CKEditor editorId.
  3. Edit text, headings, marks, and lists in both windows.
  4. Verify remote selections, version restore, reconnection, and persistence after reload.
Common issues:
  • Toolbar item unavailable: Add its CKEditor plugin to config.plugins.
  • License error: Provide GPL only for a GPL-compatible use, otherwise use your commercial license key.
  • Content does not sync: Remove controlled data updates and confirm both clients use the same document and editorId.
  • Manager never appears: Confirm the document context was set first, Velt is initialized, a user is identified, and CKEditor’s onReady fired. The document is a prerequisite, not an explicit hook wait condition.
  • Cursors not visible: Ensure cursorsContainer is not null and the editable element is mounted.
  • Stale manager after watchdog restart: Forward onAfterDestroy when using the hook directly.
Use the VeltCrdtStoreMap debugging interface to inspect the shared XML and provider state.

Complete Example

Complete App.tsx

How It Works

  1. React applications initialize Velt through an authenticated VeltProvider; other frameworks use initVelt(). Both authenticate before setting the shared document.
  2. The React hook explicitly waits for Velt initialization, an authenticated user, the CKEditor instance, and enabled. The document remains a prerequisite but is not an explicit hook wait condition.
  3. Direct integrations create CKEditor first and pass the resolved editor instance to createCollaboration().
  4. The manager opens a Velt CRDT store keyed by editorId, registers the fragment observer, and only then initializes the Store so initial remote HTML is observed.
  5. CKEditor model changes are debounced into shared transactions; remote changes update CKEditor while preserving the local selection when possible.
  6. Awareness carries user and selection metadata for remote cursor overlays, and Velt persists CRDT updates and named versions.

APIs

React: CKEditorCrdtEditor

*Provide either editorId or documentId. Other CKEditor React props pass through except CRDT-owned data and onReady.

React: useCollaboration()

Other Frameworks: createCollaboration()

CollaborationManager Methods

Exported Types and Helpers

  • CollaborationConfig, UseCollaborationConfig / UseCKEditorCrdtConfig
  • UseCollaborationReturn / CKEditorCrdtHookResult
  • CKEditorCrdtEditorProps, CKEditorCollaborationPrimitives, CKEditorEditorLike
  • CKEditorCursorData, CKEditorCursorRect, CKEditorCursorSelection, CKEditorRemoteCursor
  • SyncStatus, Unsubscribe, Version
  • normalizeHtml(), readHtmlFromXmlFragment(), writeHtmlToXmlFragment()
  • hasSharedHtml(), applyInitialContent()