Skip to main content
The Loro extension enables real-time collaborative editing using the Loro CRDT (Conflict-free Replicated Data Type) library. Loro offers excellent performance and a rich set of features for collaborative applications.

Installation

Install Loro peer dependencies:
npm install loro-crdt loro-prosemirror
The Loro extension is included in the prosekit package:
import { defineLoro } from 'prosekit/extensions/loro'

Styling

Import Loro styles to show user selections:
import 'prosekit/extensions/loro/style.css'

Usage

import { createEditor } from 'prosekit/core'
import { defineLoro } from 'prosekit/extensions/loro'
import { Loro } from 'loro-crdt'
import 'prosekit/extensions/loro/style.css'

const loro = new Loro()
const doc = loro.getMovableList('prosemirror')

const editor = createEditor({
  extension: defineLoro({ 
    loro: loro,
    doc: doc 
  })
})

API reference

defineLoro(options)

Creates a Loro extension for collaborative editing.
options.loro
Loro
required
The Loro instance to sync with.
options.doc
LoroMovableList
The Loro movable list to use for the document. Typically obtained with loro.getMovableList('prosemirror').
options.colors
{ background: string, foreground: string }[]
Color palette for user selections. Defaults to a preset palette.

Complete example

See the Collaboration guide for a complete working example with WebSocket synchronization.

Key concepts

Loro instance

A Loro instance contains shared data structures:
const loro = new Loro()

LoroMovableList

The ProseMirror document is stored as a movable list:
const doc = loro.getMovableList('prosemirror')

Network synchronization

Loro uses binary updates for efficient synchronization:
import { Loro } from 'loro-crdt'

const loro = new Loro()

// Export updates
const updates = loro.exportFrom(version)

// Apply updates
loro.import(updates)

WebSocket sync example

import { Loro } from 'loro-crdt'

const loro = new Loro()
const ws = new WebSocket('wss://your-server.com/room-id')

// Send local updates to server
loro.subscribe((event) => {
  const updates = loro.exportFrom(event.from)
  ws.send(updates)
})

// Apply remote updates
ws.onmessage = (event) => {
  const updates = new Uint8Array(event.data)
  loro.import(updates)
}

User awareness

Track user presence and selections:
// Set local user info
const userId = 'user-123'
loro.setPeerId(userId)

// Set user metadata
loro.setUserMetadata({
  name: 'Alice',
  color: '#2563eb',
})

Persistence

Save and load document state:
import { Loro } from 'loro-crdt'

// Export snapshot
const snapshot = loro.export({ mode: 'snapshot' })
localStorage.setItem('document', JSON.stringify(Array.from(snapshot)))

// Load snapshot
const stored = localStorage.getItem('document')
if (stored) {
  const snapshot = new Uint8Array(JSON.parse(stored))
  loro.import(snapshot)
}

Time travel

Loro supports history navigation:
// Get current version
const version = loro.version()

// Checkout previous version
loro.checkout(version - 1)

// Return to latest
loro.checkoutToLatest()

Custom colors

const editor = createEditor({
  extension: defineLoro({ 
    loro: loro,
    doc: doc,
    colors: [
      { background: '#fbbf2420', foreground: '#fbbf24' },
      { background: '#3b82f620', foreground: '#3b82f6' },
      { background: '#ef444420', foreground: '#ef4444' },
    ]
  })
})

Benefits over Yjs

  • Better performance - Faster operations on large documents
  • Time travel - Built-in history navigation
  • Rich data types - More CRDT types beyond XML
  • Movable lists - Better support for block reordering
  • TypeScript - First-class TypeScript support

Use cases

Collaborative documents

Multiple users editing the same document

Version history

Time travel through document history

Offline-first apps

Apps that work without internet

High-performance editing

Large documents with many users