Skip to main content
The @veltdev/monaco-crdt-react and @veltdev/monaco-crdt libraries enable real-time collaborative editing in Monaco. The collaboration engine is built on Yjs, y-monaco, 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

Ensure your application resolves one copy of yjs, y-protocols, and monaco-editor. For Vite projects, deduplicate them explicitly:

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.
Wrap the app with an authenticated VeltProvider, then set the document after the current user is available. Keep the document hook unconditional and gate the effect:

Step 3: Initialize Collaborative Editor

  • Pass an editorId to uniquely identify the shared Monaco model.
  • Do not pass React wrapper value or defaultValue, and do not seed a direct Monaco editor with non-empty local text. The CRDT-backed model owns shared content.
  • Pass initialContent as plain text for a brand-new shared document.
MonacoCrdtEditor is the simplest integration. It creates Monaco, binds its model to Y.Text, and manages collaboration cleanup.
Normal @monaco-editor/react props are passed through. The wrapper owns value, defaultValue, and the internal mount handler, but still calls your onMount callback after capturing the editor.
Do not provide React wrapper value or defaultValue, or use Monaco’s local value as a second content source. A directly constructed editor can start with value: ''; use initialContent on the collaboration manager to seed shared text.

Step 4: Status Monitoring (Optional)

Use the hook’s reactive state, component callbacks, or manager subscriptions:
Local Monaco edits continue while disconnected and synchronize when the provider reconnects.

Step 5: Version Management (Optional)

Restoring a version changes the shared Y.Text for all collaborators. Refresh the version list after each save or restore. For multi-tab interfaces, also refresh on window focus or visibility changes. If peers need immediate metadata updates, broadcast a small awareness event and refresh when they receive it.

Step 6: Force Reset Initial Content (Optional)

initialContent is inserted only when the shared text is new and empty:
For an explicit reset workflow:
forceResetInitialContent clears the existing shared text. Do not enable it during normal page loads.

Step 7: Style Remote Cursors

y-monaco adds remote selection decorations to Monaco. Add base styles:
These fallback styles keep remote selections and carets visible. To apply each collaborator’s awareness color and add name labels, create client-specific rules from awareness state:
Pass local identity with cursorData; when omitted, the adapter falls back to the authenticated Velt user.

Step 8: CRDT Event Subscription (Optional)

Step 9: Custom Encryption (Optional)

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

Step 10: Error Handling (Optional)

Manager methods return safe fallback values when collaboration resources are unavailable.

Step 11: Access Collaboration Primitives (Advanced)

The hook exposes commonly used primitives:
Use manager.getUndoManager()?.undo() and redo() for collaboration-aware history. Do not add a separate Monaco history owner for the shared model.

Step 12: Enable, Disable, and Cleanup

Delay or disable manager creation while continuing to render Monaco:
The component and hook destroy the collaboration manager automatically. The hook also returns destroy() for early teardown. If you created Monaco yourself, dispose the editor and model separately because the manager does not own them.

Step 13: Client-only Rendering

Monaco depends on browser APIs. In Next.js, load the collaborative editor without server-side rendering: Configure Monaco workers in the application bundler as well. For example, a Vite application can install the core editor worker before Monaco is created:
Add language-specific workers the same way when the configured Monaco languages require them.

Notes

  • Uncontrolled model: Do not control the wrapper with value or defaultValue. A direct Monaco instance may start empty, but the bound Y.Text owns shared content.
  • One dependency instance: Deduplicate yjs, y-protocols, and monaco-editor to avoid incompatible constructors and awareness instances.
  • Unique editorId: Collaborators must use the same Velt document context and editorId.
  • Plain-text model: Monaco content is stored in Y.Text; the Monaco language affects editing behavior, not shared data format.
  • Store identity: The manager creates a text Store with content key content and source metadata monaco.
  • Remote cursors: y-monaco supplies decorations; your CSS controls labels and colors.
  • One binding path: Passing editor to createCollaboration() auto-binds it. If you omit editor, call bindEditor(editor) later. Never do both for the same manager.
  • Multiple editors: Give independent models different editorId values. Editors sharing one model can be passed through bindEditor() options.
  • Other Frameworks lifecycle: Unsubscribe events and cursor-style listeners, destroy the manager, and then dispose the Monaco editor.
  • Client-only: Render Monaco only in the browser.
  • 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 Monaco editorId.
  3. Type, select text, undo, and redo in both windows.
  4. Save and restore a version, then reload and verify persistence.
Common issues:
  • Yjs was already imported: Deduplicate yjs and remove nested or linked duplicate copies.
  • Text does not sync: Remove value and defaultValue, and confirm both clients use the same document and editorId.
  • Editor never initializes: Confirm Monaco is mounted, Velt is ready, and a user is identified.
  • Cursor labels are missing: Add the dynamic client-ID styles or supply your own awareness renderer.
  • SSR errors: Load the Monaco component client-side only.
Use the VeltCrdtStoreMap debugging interface to inspect the shared text and provider state.

Complete Example

Complete App.tsx
Complete monaco-cursors.css

How It Works

  1. React applications initialize Velt with VeltProvider; other frameworks initialize one client with initVelt(). Both authenticate before setting the shared document.
  2. React wrappers wait for Velt, an authenticated user, and a mounted Monaco editor. Other frameworks wait for client readiness and create Monaco before collaboration.
  3. The manager creates a Velt CRDT store and obtains the shared Y.Text.
  4. Passing editor to the factory auto-binds y-monaco; alternatively, a manager created without an editor can be bound once later with bindEditor().
  5. y-monaco maps awareness selections to editor decorations. Static CSS supplies a visible fallback caret, while dynamic rules apply each collaborator’s color and label.
  6. Local text transactions merge with concurrent remote transactions, and Velt persists the Yjs update stream and named versions.

APIs

React: MonacoCrdtEditor

*Provide either editorId or documentId. Other Monaco React props are passed through except value and defaultValue.

React: useCollaboration()

Other Frameworks: createCollaboration()

This factory example uses automatic binding. For attach-later binding, omit editor and follow the separate alternative in Step 3; never combine the two paths.

CollaborationManager Methods

Exported Types

  • CollaborationConfig, CollaborationPrimitives, BindEditorOptions
  • UseCollaborationConfig / UseMonacoCrdtConfig
  • UseCollaborationReturn / MonacoCrdtHookResult
  • MonacoCrdtEditorProps, MonacoCursorData, MonacoBindingInstance
  • SyncStatus, Unsubscribe, Version