Skip to content

Rich text with Yjs

Character-level collaborative editing is the one place where last-write-wins or guarded patches are clearly the wrong tool: two people typing in the same paragraph should both win. For that, datadata embeds Yjs — a mature CRDT implementation — rather than reinventing text merging.

A document’s structured data is JSON, changed by guarded patches. Where a field needs collaborative text, it holds a reference to an embedded Yjs document:

{ "title": "Roadmap", "body": { "$yjs": "yjs_a1b2c3" } }

The Yjs document is created alongside its parent and travels with it: subscribing to the parent delivers the Yjs state too, and updates flow as binary Yjs deltas next to JSON patches in the same wire protocol events.

Clients work with real Y.Doc instances, so the whole Yjs editor ecosystem (ProseMirror/Tiptap bindings, etc.) applies directly. Local Yjs edits are batched and synced automatically.

Structured dataText
FormatJSON + RFC 6902 patchesYjs binary updates
ConcurrencyServer-ordered, guards detect conflictsCRDT, always converges
Conflict surfaceExplicit — rejected guards, staged conflict previewsNone — merging is automatic

This split is a deliberate design decision: structure benefits from explicit, reviewable conflicts; prose benefits from silent convergence.

The boundary extends to validation: the schema’s jurisdiction deliberately ends where Yjs begins. Validation governs the JSON structure (including that a field holds a Yjs reference); constraints on the collaborative text content — length, structure, formatting rules — are intentionally left to the application and its editor.