Skip to main content

Yjs

Fast collaborative editing. A high-performance CRDT library for real-time applications.

Overview

Yjs is a high-performance CRDT implementation that enables real-time collaboration in web applications. It's network-agnostic and integrates with popular text editors.

Key Features

Network Agnostic

┌─────────────────────────────────────────────────────────────────┐
│ Yjs Networking │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Yjs Document │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ y-websocket │ │ y-webrtc │ │ y-indexeddb │ │
│ │ (server) │ │ (p2p) │ │ (local) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ Same document, any transport │
│ │
└─────────────────────────────────────────────────────────────────┘

Core Features

FeatureDescription
FastOptimized for large documents
Network-agnosticWebSocket, WebRTC, or custom
Editor bindingsProseMirror, Quill, Monaco, CodeMirror
Undo/RedoBuilt-in undo manager
CursorsShared cursor positions
OfflineWorks without network

Shared Types

TypeDescription
Y.MapKey-value pairs
Y.ArrayOrdered list
Y.TextRich text with formatting
Y.XmlFragmentXML/HTML structure

Usage

Basic Example

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

// Create document
const doc = new Y.Doc()

// Get shared types
const ytext = doc.getText('content')
const yarray = doc.getArray('items')

// Connect to sync server
const provider = new WebsocketProvider(
'wss://demos.yjs.dev', 'my-room', doc
)

// Make changes
ytext.insert(0, 'Hello World')
yarray.push(['item1', 'item2'])

// Observe changes
ytext.observe(event => {
console.log('Text changed:', ytext.toString())
})

Editor Integration

import * as Y from 'yjs'
import { QuillBinding } from 'y-quill'
import Quill from 'quill'

const doc = new Y.Doc()
const ytext = doc.getText('quill')

const editor = new Quill('#editor')
const binding = new QuillBinding(ytext, editor)

// Now multiple users can edit collaboratively

Architecture

┌─────────────────────────────────────────────────────────────────┐
│ Yjs Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Editor A │ │ Editor B │ │ Editor C │ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ ┌───────┴───────┐ ┌───────┴───────┐ ┌───────┴───────┐ │
│ │ Binding │ │ Binding │ │ Binding │ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌─────────┴─────────┐ │
│ │ Y.Doc │ │
│ │ (shared state) │ │
│ └─────────┬─────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Provider │ │ Provider │ │ Provider │ │
│ │ (network) │ │ (storage) │ │ (p2p) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

Providers

ProviderPurpose
y-websocketWebSocket sync server
y-webrtcPeer-to-peer WebRTC
y-indexeddbBrowser persistence
y-leveldbNode.js persistence
y-redisRedis pub/sub

Editor Bindings

BindingEditor
y-prosemirrorProseMirror
y-quillQuill
y-monacoMonaco (VS Code)
y-codemirrorCodeMirror
y-textareaPlain textarea

Performance

Yjs is optimized for:

AspectOptimization
Large documentsEfficient encoding
Many usersMinimal sync overhead
High frequencyBatched updates
MemoryGarbage collection

Comparison

FeatureYjsAutomergeShareDB
SpeedFastestFastMedium
P2PYesYesNo
Editor bindingsManyFewSome
HistoryLimitedFullVia OT
JSON-likePartialYesYes

Use Cases

  1. Collaborative editors — Google Docs-like apps
  2. Whiteboards — Real-time drawing
  3. Code editors — Pair programming
  4. Design tools — Collaborative Figma-like
  5. Forms — Shared input

Security

For end-to-end encryption:

// Encrypt sync messages
import { encodeStateAsUpdate, applyUpdate } from 'yjs'

// Export state
const state = encodeStateAsUpdate(doc)

// Encrypt before sending
const encrypted = encrypt(state, key)

// On receive: decrypt then apply
const decrypted = decrypt(encrypted, key)
applyUpdate(doc, decrypted)

See Also