mrkeyoor.com_
Wed 23 Sept 07:31 UTC
npmWeb Frontendupdated 23 Sept 2026

@lexical/yjs review

@lexical/yjs 0.49.0 converts Lexical editor updates into Yjs shared types and applies remote Yjs changes back to Lexical. Its public pieces cover the binding, collaborative undo, awareness state, remote cursor positions, connection commands, and low-level synchronization calls. React projects usually let `CollaborationPlugin` from `@lexical/react` manage those pieces. The package supplies no network transport, server, room authorization, persistence, or identity system. The 0.49.0 family also completes Lexical's `$config()` migration for built-in nodes, so collaborative custom-node tests should run with the whole Lexical set on 0.49.0.

Verdict

@lexical/yjs 0.49.0 installed as 7 packages using 13 MB and produced a 96.1 KB gzipped browser bundle in our sandbox, so the binding is measurable weight rather than a free Lexical add-on. Install it when Yjs is already the collaboration model; otherwise choose the editor binding that matches your actual document engine.

We installed it

Lab card: what happened when we installed @lexical/yjsScreenshot of @lexical/yjs documentation
Install✓ · 3.6s7 packages on disk · 13 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser96.1 KBgzipped (298 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @lexical/yjs install cleanly?

Yes. In a fresh container with an empty cache, npm install @lexical/yjs finished in 4 seconds, leaving 7 packages and 13 MB on disk. npm audit reported no known vulnerabilities.

How much does @lexical/yjs add to a browser bundle?

96.1 KB gzipped (298 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does @lexical/yjs work with both ESM and CommonJS?

Yes. Both import '@lexical/yjs' and require('@lexical/yjs') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does @lexical/yjs include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

@lexical/yjs or y-prosemirror: which should you use?

y-prosemirror: Use it when the editor model is ProseMirror and Yjs should bind directly to ProseMirror state. @lexical/yjs 0.49.0 installed as 7 packages using 13 MB and produced a 96.1 KB gzipped browser bundle in our sandbox, so the binding is measurable weight rather than a free Lexical add-on.

When should you not use @lexical/yjs?

You expect one install to produce a collaboration service. @lexical/yjs has no WebSocket server, database adapter, authentication, or access-control layer.

API stability3/5@lexical/yjs 0.49.0 retains its V1 binding, undo manager, awareness helpers, connection commands, and synchronization functions. The package is still on a 0.x line and exports V2 binding, V2 sync, rendered snapshots, and version-diff commands with `__EXPERIMENTAL` labels. It also pins `lexical`, `@lexical/internal`, and `@lexical/selection` to exactly 0.49.0. Treat the coordinated Lexical release as the upgrade unit and test 2-client edits, undo, reconnect, and custom nodes together.
Docs4/5Lexical publishes a generated API reference plus a React collaboration guide that covers the 7-package example install, y-websocket server command, provider factory, `editorState: null`, `shouldBootstrap`, custom-node property discovery, and NodeState. The package README itself contains only 1 descriptive sentence and an API link. Teams building without React must infer observer registration, awareness, undo, reload handling, and teardown from source and hooks rather than one complete low-level recipe.
Maintenance5/5npm published 0.49.0 on July 30, 2026, and GitHub reports a push on August 25, 2026. The Meta-maintained repository is not archived, has 23,793 stars, and ships coordinated monthly releases across the Lexical monorepo. GitHub's 336 open issues and pull requests cover the entire editor project rather than this package alone. Active work is clear, although consumers inherit the monorepo's release cadence and should expect binding upgrades to arrive with wider editor changes.
Ecosystem5/5The npm endpoint counted 4,734,375 downloads from August 18 through 24, 2026. @lexical/yjs connects Lexical's editor and React packages to Yjs shared documents, awareness, undo, offline merging, and provider ecosystem. The official React plugin removes much lifecycle work. Guaranteed transport choice is narrower: Lexical names y-websocket as its only officially supported provider, and this binding ships no persistence or access-control adapter of its own.

Use it if

  • Your editor is Lexical, your shared document is Yjs, and you want the binding maintained in the Lexical monorepo.
  • Remote selections, awareness metadata, connection commands, and Yjs-backed undo belong in the editing experience.
  • A React application can use the documented CollaborationPlugin lifecycle instead of wiring every observer by hand.
  • Your team already owns authenticated room transport, durable document storage, reconnect policy, and server-side initialization.
Skip it if

Setup reality

Our Node 22 sandbox installed @lexical/yjs 0.49.0 in 3.6 seconds. The result was 7 packages and 13 MB on disk; @lexical/yjs itself was 700 KB unpacked. npm audit found 0 known vulnerabilities. The manifest has 3 direct dependencies and 2 peers: Yjs >=13.5.22 and TypeScript >=5.2. It includes TypeScript declarations and an exports map. Both CommonJS require() and ESM import worked against the CommonJS package in our checks.

Installation does not create a working room. A React setup also needs Lexical, @lexical/react, React, Yjs, and a provider. Lexical documents y-websocket as the only officially supported connection provider, while noting that others may work. The provider factory receives the room ID and a shared Map<string, Y.Doc>; it must put the document into that map. With y-websocket, pass connect: false so CollaborationPlugin owns connect and disconnect. Authenticate the socket and authorize every room on the server.

Initial state is the costly surprise. Set the Lexical composer's editorState to null, then initialize production Yjs documents on the server and use shouldBootstrap={false}. The guide reserves client bootstrapping for local development because 2 clients can race to seed the same empty room. Awareness fields travel as presence data and disappear with the session. They are neither durable profile storage nor trusted proof of identity. Persistence, retention, document deletion, and recovery remain provider concerns.

Custom nodes need a collaboration audit during the 0.49.0 upgrade. The binding discovers synchronized fields by constructing a node and inspecting its own enumerable properties. Assign optional fields in every constructor path, even when the value is undefined, or use NodeState. Our full browser import measured 298 KB minified and 96.1 KB gzipped, so load collaboration only on editor routes. Low-level callers must register both Lexical and Yjs observers, awareness, undo, connection state, teardown, and error handling themselves.

Patterns

Install one Lexical release across the editor install-matched-stack

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

@lexical/yjs 0.49.0 pins 3 Lexical dependencies to 0.49.0 and declares Yjs `>=13.5.22` plus TypeScript `>=5.2` as peers.

Create and cache the room document create-websocket-provider

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

const providerFactory = useCallback((id, docs) => {
  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},
  );
}, []);

`connect: false` leaves connection control to CollaborationPlugin; the server must authenticate the socket and authorize the room ID.

Let the Yjs document initialize Lexical disable-local-editor-state

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

`editorState: null` prevents a second local state from racing the collaborative document during startup.

Mount the managed React lifecycle mount-react-collaboration

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

<LexicalCollaboration>
  <LexicalComposer initialConfig={initialConfig}>
    <CollaborationPlugin
      id={documentId}
      providerFactory={providerFactory}
      shouldBootstrap={false}
    />
  </LexicalComposer>
</LexicalCollaboration>

CollaborationPlugin disconnects its provider during cleanup; `shouldBootstrap={false}` assumes the production document is initialized elsewhere.

Seed a development-only room bootstrap-local-room

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

Two production clients can race when `shouldBootstrap` is true, so Lexical's guide directs production initialization to the server.

Attach visible collaborator details publish-awareness-metadata

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

Awareness data is ephemeral and visible to peers; it is not durable user storage or proof that the claimed user ID is authorized.

Track connection changes in the editor watch-connection-state

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

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

Call `removeListener()` during cleanup; each registration adds another command handler to the editor.

Pause and resume transport toggle-provider-connection

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

editor.dispatchCommand(TOGGLE_CONNECT_COMMAND, false);
editor.dispatchCommand(TOGGLE_CONNECT_COMMAND, true);

A disconnect changes transport state only; it does not revoke room access, erase the local Y.Doc, or block local edits.

Create an enumerable optional property sync-custom-node-field

class MentionNode extends TextNode {
  __userId: string | undefined;

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

The assignment creates an own enumerable property even when the value is `undefined`; a TypeScript `?` declaration alone may emit no field.

Bind one editor to one Yjs document create-low-level-binding

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

const id = 'article-42';
const doc = new Y.Doc();
const docMap = new Map([[id, doc]]);

const binding = createYjsBinding({
  editor,
  id,
  doc,
  docMap,
});

`createYjsBinding()` creates binding state only; low-level code still needs editor updates, Yjs observers, awareness, undo, and teardown.

Choose the Yjs root key explicitly use-named-shared-root

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

Version 0.49.0 defaults V1 bindings to root key `root`; every peer must use the same key to edit the same shared type.

Scope undo to the shared root create-collaborative-undo

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

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

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

The helper tracks the binding and `null` origins for one shared root; the React plugin already wires collaborative history in its managed flow.

Alternatives

PackageRegistryPick it when
y-prosemirrornpmUse it when the editor model is ProseMirror and Yjs should bind directly to ProseMirror state.
@tiptap/extension-collaborationnpmUse it for Tiptap projects that want collaboration exposed through Tiptap extensions.
y-quillnpmUse it when an existing Quill editor needs a Yjs binding.
@lexical/reactnpmUse its CollaborationPlugin above this package when React should own provider and binding cleanup.

More web frontend guides

postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.