Yjs is the best CRDT implementation in JavaScript, and it is not close. 31.8 million downloads a month, a decade of work behind it, and a conflict-resolution engine so solid that most of the collaborative editors you have used are running it underneath. Nothing in this article disputes that.
What this article is about is a different question, and it is the one that decides your architecture: Yjs is a data structure, not an application. It merges concurrent edits. It does not store anything, does not talk to anyone, does not know who the user is and cannot tell you whether they were allowed to make that change. Every one of those lives in a separate package.
That division is a legitimate design — do one thing well — and for years the ecosystem around it filled the gaps. The problem is what has happened to that ecosystem lately.
What Yjs Actually Gives You
Yjs provides shared types — Y.Map, Y.Array, Y.Text, Y.XmlFragment — that converge automatically when several people edit them at once. Feed it updates from anywhere, in any order, possibly duplicated, and every replica ends up identical. That is a genuinely hard problem and Yjs solved it well enough to become the default.
It is also, deliberately, all it does. Persistence, transport, identity and authorization are somebody else's job — specifically, the providers.
The Provider Problem
This is the part that rarely makes it into comparisons. To turn Yjs into a working application you assemble packages, and their maintenance status is not uniform:
| Package | Role | Unpacked | Latest npm release | Repo activity |
|---|---|---|---|---|
yjs |
The CRDT itself | 2,253 KB | current | active |
y-websocket |
Sync via your own server | 92 KB | Aug 2026 | active |
y-webrtc |
Peer-to-peer sync | 1,848 KB | Dec 2023 | last push Apr 2024 |
y-indexeddb |
Local persistence | 1,097 KB | Nov 2023 | last push Feb 2025 |
y-protocols |
Encoding | 1,045 KB | — | active |
Read that table again, because the shape of it matters more than any single row: the only actively released provider is the one that requires you to run a server. The two that would give you peer-to-peer sync and local persistence have not shipped a release in nearly three years, and y-webrtc has 22 open issues waiting.
Assembled, the browser-and-P2P stack — yjs + y-indexeddb + y-webrtc + y-protocols — comes to roughly 6.2 MB unpacked across four packages, and still leaves identity, permissions and queries unanswered. GenosDB is 2 MB in one package with zero dependencies, with those three included.
There is nothing dishonest about this — these are volunteer packages in a large ecosystem, and y-webrtc still works for many people. But if your plan was "Yjs plus y-webrtc, no backend", you are building on two packages whose last release predates a great deal of what has changed in browsers since.
Even the community forum reflects the strain: an issue opened in June 2026 reports that discuss.yjs.dev went down because its Let's Encrypt certificate expired.
What the Open Issues Say
Yjs has 139 open issues. Most are small. These are not:
"Nest Y types inside JSON" — open since November 2020, 27 reactions. The most-requested change in the repository, six years old. Y types cannot be nested inside plain JSON values, so your data model has to be expressed in Yjs's vocabulary rather than in your own.
"TypeScript enhancement" — open since January 2023, 23 reactions — and "Improve typescript typing of .get()" — open since November 2021, 18 comments. Y.Map.get() cannot tell you what it returns. In practice you cast.
"Misordered updates result in temporarily missing Y.Map keys" — open since October 2023, 11 comments. Under certain orderings, keys can briefly vanish from a Y.Map. Temporary, but a correctness surprise in a library whose whole promise is convergence.
"Y.Map get does not return prelim content" — open since June 2020, 11 comments. Reading back what you just wrote does not always give you what you wrote.
What You Still Have to Build
Suppose the providers work perfectly. Here is what Yjs still does not answer, by design:
- Who is this user? Yjs has no identity. Awareness carries a nickname and a cursor colour; it is not authentication.
- Were they allowed to do that? There is no authorization layer. Any peer holding the document can modify any part of it. If you need "editors may write, viewers may not", you build it, and you build it somewhere trusted — which usually means the server you were trying to avoid.
- Where do I query? Yjs has no query language. You walk the structure in JavaScript. There is no "give me the open tasks assigned to me, sorted by priority".
- How do I relate things? Y types nest inside each other, but there are no relations or traversal between documents.
None of this is a flaw. It is what "a CRDT library" means. The question is only whether you want to assemble the rest yourself.
What Arrives in One Package Instead
GenosDB starts from the other end: it is a database that happens to synchronise, rather than a synchroniser you build a database around.
import { gdb } from 'genosdb'
const db = await gdb('my-room', { rtc: true })
That single line brings persistence (OPFS), peer-to-peer transport (WebRTC through GenosRTC), peer discovery over the public Nostr relay network — no signaling server of your own — cryptographic identity via WebAuthn, and a query engine. Zero dependencies, so there are no satellite packages whose release cadence you have to track.
Identity and permissions are part of the database. The Security Manager signs every operation with the author's key, and every peer verifies it independently. Role-based access control — guest, user, manager, admin, superadmin — plus per-node ACLs and rule-based governance. "Editors may write, viewers may not" is enforced by signature, with no trusted server anywhere.
Queries go to an engine, not to a for loop:
db.map({
query: { status: 'open', assignee: me, priority: { $gte: 3 } },
order: 'desc',
$limit: 20
}, onRow) // live: re-fires as peers write
$eq $ne $gt $gte $lt $lte $in $between $exists $startsWith $endsWith $contains $text $like $regex $not $and $or $edge $near, with cursor pagination and $edge for recursive graph traversal.
And convergence still happens — without a CRDT library. The mechanism deserves its own article, and it has one: GenosDB vs Yjs and Automerge: real-time collaboration without a CRDT library walks through the ordering model, one node per paragraph, and why the path is shorter. This article is about the packaging; that one is about the algorithm.
The Same App, Both Ways
A collaborative document that persists locally and syncs peer-to-peer.
Yjs:
import * as Y from 'yjs'
import { WebrtcProvider } from 'y-webrtc' // last release: Dec 2023
import { IndexeddbPersistence } from 'y-indexeddb' // last release: Nov 2023
const doc = new Y.Doc()
new IndexeddbPersistence('my-room', doc)
new WebrtcProvider('my-room', doc, {
signaling: ['wss://your-signaling-server.example']
})
const blocks = doc.getArray('blocks')
blocks.observe(() => render(blocks.toArray()))
blocks.push([{ text: 'hello' }])
// identity, permissions and queries: not included
GenosDB:
import { gdb } from 'genosdb'
const db = await gdb('my-room', { rtc: true })
db.map({ order: 'desc' }, ({ id, value, action }) => render(id, value, action))
await db.put({ text: 'hello' })
// identity, permissions and queries: already here
Side by Side
| Yjs | GenosDB | |
|---|---|---|
| What it is | CRDT library | Database with sync built in |
| Persistence |
y-indexeddb (Nov 2023)
|
OPFS, native |
| P2P transport |
y-webrtc (Dec 2023)
|
GenosRTC, native |
| Server-free discovery | Signaling server of your own | Public Nostr relays |
| Identity | None (awareness ≠ auth) | WebAuthn or mnemonic, signed ops |
| Permissions | None | RBAC + per-node ACLs + governance |
| Queries | Walk the structure in JS | Full operator set, reactive |
| Relations | Nested types only | Graph with $edge traversal |
| Packages to track | 4+ | 1, zero dependencies |
| Conflict resolution | CRDT | Signed ops + Hybrid Logical Clocks |
What This Looks Like Running
All of it in the browser, nothing deployed behind it.
Collaborative block editor — two browsers, same document:
dCode — branches, commits and pull requests, peer-to-peer:
A collaborative whiteboard in a single file:
When Yjs Is the Right Choice
- You are integrating a rich-text editor. ProseMirror, TipTap, Lexical, CodeMirror, Monaco, Slate — Yjs has mature, battle-tested bindings for all of them. If character-level collaborative rich text is the product, use Yjs.
-
You already have a backend and want
y-websocket. That path is actively maintained and well documented, and the server does identity and permissions for you. - You need the strongest possible merge guarantees on text. Yjs's CRDT has a decade of adversarial use behind it.
- You want an open-source dependency. Yjs is MIT. GenosDB's bundle is free for personal and commercial use but the source is proprietary by deliberate governance choice — it is not open source, and saying otherwise would be inaccurate.
GenosDB is the better fit when the document is not the whole product: when you also need identity, permissions, relations and queries, and would rather not assemble four packages — two of them without a release since 2023 — to get there.
A Note on Where This Comes From
I have spent years building browser P2P databases. I wrote the official GUN documentation platform and contributed to that codebase before starting GenosDB, and every decision described here came from hitting these walls in real projects: a merge engine that worked beautifully and left me writing the authentication, the persistence layer and the permission checks by hand.
Yjs did not fail at any of that — it never promised it. What changed is that assembling the rest is no longer free, now that the packages you would assemble are three years between releases.
If anything here is out of date or wrong, tell me and I will correct it — GitHub Discussions is open and I answer.
Originally published at genosdb.com/yjs.
⭐ Found this useful? Star GenosDB on GitHub — or spin it up in seconds: npm i genosdb.
This article is part of the official documentation of GenosDB (GDB).
GenosDB is a distributed, modular, peer-to-peer graph database built with a Zero-Trust Security Model, created by Esteban Fuster Pozzi (estebanrfp).
📄 Whitepaper | overview of GenosDB design and architecture
🛠 Roadmap | planned features and future updates
💡 Examples | code snippets and usage demos
📖 Documentation | full reference guide
🔍 API Reference | detailed API methods
💬 GitHub Discussions | community questions and feedback
🗂 Repository | Minified production-ready files
📦 Install via npm | quick setup instructions



Top comments (0)