mrkeyoor.com_
Sat 08 Aug 21:01 UTC
npmWeb Frontendupdated 08 Aug 2026

@lexical/yjs

@lexical/yjs is the CRDT binding between a Lexical editor and a Yjs document. It translates Lexical node and selection changes into Yjs shared data, applies remote Yjs updates back to the editor, tracks collaborator awareness and cursor positions, and creates a Yjs-aware undo manager. Most React applications consume it indirectly through `CollaborationPlugin` from @lexical/react; it does not provide networking, a collaboration server, persistence, authentication, or access control.

Verdict

The right binding when the editor is Lexical and the collaboration model is Yjs, with first-party React integration and active releases. Budget for the provider, persistence, security, bootstrapping, version alignment, and custom-node audits; this package handles editor synchronization, not the collaboration product around it.

API stability3/5The main React collaboration flow and V1 binding have recognizable long-running concepts, but the package remains at 0.49.0, pins internal Lexical packages to the identical version, and exports APIs carrying `__EXPERIMENTAL` names for V2 bindings, sync, and snapshots. Even routine upgrades should move the Lexical family together and run multi-client editing tests rather than assuming semver-level isolation.
Docs4/5The collaboration guide supplies a full React and y-websocket setup, explains editorState null, provider factories, client versus server bootstrapping, provider support, custom-node enumerable properties, and production caveats. The generated API reference is complete. The package README itself is only one sentence, and non-React lifecycle wiring is scattered across source rather than taught as an end-to-end guide.
Maintenance5/5Version 0.49.0 was published on July 30, 2026, and GitHub reports a Lexical repository push on August 8, 2026. The non-archived MIT project has frequent coordinated releases, broad test infrastructure, and active development by Meta and contributors. The repository's 370 open issues and pull requests reflect the size of the full editor monorepo, not this binding alone.
Ecosystem5/5The package recorded 4,314,575 downloads in the measured week and sits at the official intersection of Lexical's editor ecosystem and Yjs providers, persistence adapters, awareness protocol, and offline CRDT tooling. First-party @lexical/react hooks and CollaborationPlugin reduce application wiring, while y-websocket is the only provider the Lexical guide calls officially supported, which narrows guaranteed transport compatibility.

Use it if

  • You are building real-time collaborative editing on Lexical and have chosen Yjs as the shared document model
  • You want the official binding used by Lexical's React CollaborationPlugin and playground
  • You need collaborator cursors, awareness state, connection commands, and Yjs-scoped undo history
  • You can own the provider, room naming, server persistence, authentication, and initial document lifecycle
Skip it if

Setup reality

The minimal React install from the official guide is npm install @lexical/react @lexical/yjs lexical react react-dom y-websocket yjs. TypeScript users need 5.2 or later, Yjs must be at least 13.5.22, and the Lexical packages should all use exactly 0.49.0 because this package pins its Lexical dependencies to that version. There are no native builds or config files in the binding itself, but a working system needs much more: a Yjs provider, a WebSocket or other transport service, stable room IDs, authentication, authorization, persistence, retry and offline policy, monitoring, and cleanup. The Lexical docs say y-websocket is the only officially supported provider, though compatible providers may work. In the editor config set `editorState: null`; otherwise Lexical may create its own initial state before collaboration takes control. A provider factory must create or retrieve the Y.Doc in the supplied map and should use `connect: false`, because CollaborationPlugin manages connection and disconnection. In production, initialize the shared document on the server and pass `shouldBootstrap={false}`. The docs reserve `shouldBootstrap={true}` plus an initial editor state for local testing because two clients bootstrapping at once can corrupt content. Awareness is ephemeral presence, not durable user data. Custom node properties sync only when they are own enumerable properties assigned during construction, unless you use NodeState. The binding does not secure room names or provider messages. Your server must decide who may join and what document each ID maps to. For non-React use, the exported binding and sync primitives are low level; you must wire editor updates, Yjs observers, awareness, undo, teardown, and errors yourself, so study the React implementation before reproducing it.

Patterns

Install the documented React collaboration stackinstall-react-stack

npm install @lexical/react@0.49.0 @lexical/yjs@0.49.0 lexical@0.49.0 react react-dom y-websocket yjs

Keep Lexical packages aligned. @lexical/yjs 0.49.0 requires Yjs 13.5.22 or later and TypeScript 5.2 or later when using types.

Create a memoized y-websocket provider factorycreate-provider-factory

import {useCallback} from 'react';
import * as Y from 'yjs';
import {WebsocketProvider} from 'y-websocket';

const providerFactory = useCallback((id: string, docs: Map<string, Y.Doc>) => {
  let doc = docs.get(id);
  if (!doc) {
    doc = new Y.Doc();
    docs.set(id, doc);
  }
  return new WebsocketProvider('wss://collab.example.com', id, doc, {connect: false});
}, []);

Use an authenticated server and authorize the room ID. connect false lets CollaborationPlugin control connection lifecycle.

Let collaboration own initial editor stateconfigure-collab-editor

const initialConfig = {
  namespace: 'DocumentEditor',
  editorState: null,
  nodes: [MyNode],
  onError(error: Error) { throw error; },
  theme: {},
};

editorState must be null so Lexical does not initialize competing local content before the Yjs document loads.

Mount the React collaboration integrationmount-collaboration-plugin

import {LexicalCollaboration} from '@lexical/react/LexicalCollaborationContext';
import {CollaborationPlugin} from '@lexical/react/LexicalCollaborationPlugin';

<LexicalCollaboration>
  <LexicalComposer initialConfig={initialConfig}>
    <RichTextPlugin contentEditable={<ContentEditable />} ErrorBoundary={LexicalErrorBoundary} />
    <CollaborationPlugin
      id={documentId}
      providerFactory={providerFactory}
      shouldBootstrap={false}
    />
  </LexicalComposer>
</LexicalCollaboration>

Use shouldBootstrap false for production and initialize the Y.Doc on the server.

Bootstrap content for a local-only demobootstrap-local-demo

<CollaborationPlugin
  id="demo-room"
  providerFactory={providerFactory}
  initialEditorState={$initialEditorState}
  shouldBootstrap={true}
/>

The official guide limits client bootstrapping to development; simultaneous production clients can both initialize and corrupt content.

Observe provider connection status through Lexicaltrack-connection-status

import {CONNECTED_COMMAND} from '@lexical/yjs';
import {COMMAND_PRIORITY_LOW} from 'lexical';

const unregister = editor.registerCommand(
  CONNECTED_COMMAND,
  (connected) => { setConnected(connected); return false; },
  COMMAND_PRIORITY_LOW,
);

Call the unregister function during component cleanup to avoid accumulating command handlers.

Disconnect and reconnect collaborationtoggle-connection

import {TOGGLE_CONNECT_COMMAND} from '@lexical/yjs';

editor.dispatchCommand(TOGGLE_CONNECT_COMMAND, false); // disconnect
editor.dispatchCommand(TOGGLE_CONNECT_COMMAND, true);  // reconnect

Disconnecting transport does not authorize edits or erase local Yjs state; enforce access on the provider server.

Attach ephemeral collaborator metadataattach-awareness-data

<CollaborationPlugin
  id={documentId}
  providerFactory={providerFactory}
  shouldBootstrap={false}
  username={user.displayName}
  cursorColor={user.cursorColor}
  awarenessData={{userId: user.id, avatarUrl: user.avatarUrl}}
/>

Awareness is broadcast presence, not durable storage or trusted identity. Avoid secrets and verify permissions server-side.

Initialize every synchronized custom node propertysync-custom-node-property

class MentionNode extends TextNode {
  __userId: string | undefined;

  constructor(text: string, userId?: string, key?: NodeKey) {
    super(text, key);
    this.__userId = userId;
  }
}

An optional TypeScript declaration alone may create no own property. Assign it in every constructor path or use NodeState.

Create a V1 binding without Reactcreate-low-level-binding

import {createYjsBinding} from '@lexical/yjs';
import * as Y from 'yjs';

const id = 'document-42';
const doc = new Y.Doc();
const docMap = new Map([[id, doc]]);
const binding = createYjsBinding({editor, id, doc, docMap});

This creates binding state only. Non-React callers still must wire Lexical updates, Yjs observers, awareness, undo, connection, and teardown.

Create a Yjs-scoped undo managercreate-collab-undo-manager

import {createUndoManager} from '@lexical/yjs';

const undoManager = createUndoManager(
  binding,
  binding.root.getSharedType(),
);

undoManager.undo();
undoManager.redo();

The React CollaborationPlugin wires collaborative history for you; use this low-level helper only when managing the binding lifecycle yourself.

Use a custom Yjs shared root namecustomize-shared-root

const binding = createYjsBinding({
  editor,
  id: documentId,
  doc,
  docMap,
  rootName: 'article-body',
});

Every peer for a document must use the same rootName or they will edit different shared types.

Alternatives

PackageRegistryPick it when
y-prosemirrornpmYour editor is ProseMirror or a ProseMirror-based framework and you want its established Yjs binding
@tiptap/extension-collaborationnpmYou use Tiptap and want its packaged Yjs collaboration extension and editor integration
y-quillnpmYou need Yjs collaboration for an existing Quill editor
@lexical/reactnpmYou use React and want the higher-level CollaborationPlugin that manages this binding's lifecycle