Skip to main content
Enable real-time collaborative editing on TinyMCE Editors. The collaboration editing engine is built on top of Yjs and 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

Install the packages for your application:
The React package provides the drop-in component and hook. The base @veltdev/tinymce-crdt package provides the imperative manager and 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: Load TinyMCE

You can load TinyMCE from your own bundle or from Tiny Cloud.
Import TinyMCE, the theme, model, icons, plugins, and skin styles before rendering the editor:
Set licenseKey="gpl" on the editor when using TinyMCE under the GPL license.The default TinyMCE iframe mode uses its own content stylesheet. For self-hosted inline mode, also bundle the inline content styles and tell TinyMCE not to load skin files by URL:

Step 4: Initialize Collaborative Editor

  • Pass an editorId to uniquely identify the shared editor.
  • Use initialContent only as the seed for a brand-new shared document.
  • Do not pass value, initialValue, or onEditorChange; the CRDT manager owns TinyMCE content.
TinyMceCrdtEditor is the simplest integration. It captures TinyMCE’s editor instance, waits for Velt and the current user, initializes collaboration, and cleans up automatically.
The component forwards normal @tinymce/tinymce-react props except value, initialValue, onEditorChange, and onInit, which are owned by the collaboration layer.

Apply Formatting Programmatically

TinyMCE formatting synchronizes as part of the shared HTML. Use TinyMCE commands for toolbar actions or custom controls:
Enable the lists and link plugins for their corresponding commands. forecolor maps to the text-color toolbar control, while hilitecolor maps to backcolor.

Step 5: Status Monitoring (Optional)

The hook returns reactive status, isSynced, and error state. The component exposes equivalent callbacks, and the manager supports event subscriptions.
With the hook:
onStatusChange(), onSynced(), and onRemoteCursorsChange() immediately invoke the callback with the current value when subscribed, then invoke it again for subsequent changes. Store and call every returned unsubscribe function.
Local editing remains available while the provider reconnects. Use status for UI feedback rather than as a typing lock.

Step 6: Version Management (Optional)

Save, list, and restore named CRDT snapshots with the hook or manager:
Restoring a version replaces the shared state for every collaborator. Version metadata is not itself synchronized through the editor document, so refresh it after another user saves or restores a version.

Step 7: Force Reset Initial Content (Optional)

initialContent is HTML and is applied once when the shared document is empty:
Set forceResetInitialContent only for an explicit reset workflow:
forceResetInitialContent replaces the existing shared HTML and clears current user edits.

Step 8: Configure Remote Cursors (Optional)

The adapter publishes the local selection through Yjs awareness and renders remote caret and selection overlays automatically.
Render overlays into a specific element with cursorsContainer, or set it to null to keep awareness active without injected cursor DOM:
Subscribe to normalized remote cursor data for custom UI:
Override the generated cursor labels with normal CSS when needed:

Step 9: CRDT Event Subscription (Optional)

Listen for real-time CRDT data events using getCrdtElement().on() from the Velt client.

Step 10: Custom Encryption (Optional)

Encrypt CRDT data before it is stored in Velt by registering a custom encryption provider. For CRDT methods, input data is of type Uint8Array | number[].
See also: setEncryptionProvider() · VeltEncryptionProvider · EncryptConfig · DecryptConfig

Step 11: Error Handling (Optional)

Pass onError to receive non-fatal initialization, sync, recovery, and content errors:
Manager getters and version methods return safe fallback values when data is unavailable.

Step 12: Access Yjs Internals (Advanced)

Use the manager only when you need lower-level integration points:
You can also read or replace the canonical shared HTML:

Step 13: Cleanup

The React component and hook destroy the collaboration manager automatically. The hook also returns destroy() for early teardown.

Notes

  • Uncontrolled editor: The CRDT manager is the only owner of TinyMCE content; do not pass controlled content props.
  • Unique editorId: Collaborators must share the same Velt document context and editorId.
  • HTML data model: The adapter stores normalized HTML in a Yjs XML fragment and preserves the local selection while applying remote HTML.
  • Debounced writes: Local editor changes are serialized after debounceMs (125 ms by default).
  • Remote cursors: Cursor overlays work with iframe and inline TinyMCE modes; set cursorsContainer={null} for custom rendering.
  • Initial content: Seed content is applied once unless forceResetInitialContent is enabled.
  • Editor modes: The package supports both inline and iframe-mode TinyMCE editors.
  • Other Frameworks lifecycle: Create TinyMCE before collaboration; unsubscribe listeners and destroy the manager before removing TinyMCE.
  • Production authentication: Use Velt’s recommended server-backed authentication flow; never expose privileged server tokens in browser code.

Testing and Debugging

To test collaboration:
  1. Log in as two unique users in separate browser profiles.
  2. Open the same Velt document and TinyMCE editorId in both.
  3. Type and format content in one window and verify it appears in the other.
  4. Verify remote caret labels, selections, version restore, and persistence after reload.
Common issues:
  • Editor not syncing: Confirm the user is identified, the Velt document is set, and both clients use the same editorId.
  • Editor remains blank: Confirm all required self-hosted TinyMCE imports are loaded, or provide a Tiny Cloud API key.
  • Local content is overwritten: Remove TinyMCE value, initialValue, and onEditorChange props.
  • Cursors not appearing: Confirm both users are active and cursorsContainer was not set to null.
  • Existing content resets: Do not enable forceResetInitialContent during normal loads.
Enable browser console warnings for detailed diagnostics. You can also use the VeltCrdtStoreMap debugging interface to inspect CRDT stores in real time.

Complete Example

Complete App.tsx

How It Works

  1. Initialize and authenticate Velt through VeltProvider in React or initVelt() and identify() in other frameworks, then set a stable document context.
  2. Create TinyMCE before collaboration. The React component captures its editor automatically; the hook and framework-agnostic factory receive the existing instance.
  3. The manager opens a Velt CRDT store keyed by editorId and stores normalized HTML in a Yjs XML fragment.
  4. Local TinyMCE changes are debounced and written to the shared document. Remote changes update TinyMCE while preserving the local bookmark or DOM selection.
  5. Yjs awareness carries user identity and selection data for remote cursor overlays in iframe and inline modes.
  6. Velt persists CRDT updates and named versions. Restoring a version refreshes TinyMCE and broadcasts the restored state.
  7. React wrappers destroy collaboration automatically. Other frameworks unsubscribe listeners, destroy the manager, and then remove the TinyMCE editor explicitly.

APIs

React: TinyMceCrdtEditor

*Provide either editorId or documentId. Other supported TinyMCE React props are passed through, excluding the CRDT-owned content and initialization props.

React: useCollaboration()

The hook accepts the base CollaborationConfig without editor and with an optional veltClient.

Other Frameworks: createCollaboration()

CollaborationManager Methods

Exported Types and Helpers

  • CollaborationConfig
  • UseCollaborationConfig / UseTinyMceCrdtConfig
  • UseCollaborationReturn / TinyMceCrdtHookResult
  • TinyMceCrdtEditorProps
  • TinyMCEEditorLike
  • TinyMCECursorData, TinyMCECursorRect, TinyMCECursorSelection, TinyMCERemoteCursor
  • SyncStatus, Unsubscribe, Version
  • normalizeHtml(), readHtmlFromXmlFragment(), writeHtmlToXmlFragment()
  • hasSharedHtml(), applyInitialContent()