Skip to main content
Convex makes basic real-time sync free — every useQuery is a subscription. Some features need more than basic sync: presence, optimistic updates, collaborative editing. The patterns:

Pattern: presence

“Show who’s currently viewing this document.”
convex/schema.ts
convex/presence.ts
Client calls heartbeat every 10 seconds; list shows who’s active in the last 30s. A scheduled function cleans up stale rows nightly.

Pattern: collaborative editing

Two approaches:

Operational transformation (OT)

Each edit is a transform. Server reconciles concurrent edits. Complex; use a library (Yjs, Automerge).

Last-write-wins per field

Each field is updated independently; last write wins. Simple; works for forms and structured docs.
For most apps, the second approach is enough. For Google-Docs-class collaborative text editing, integrate Yjs with a Convex sync provider.

Pattern: live cursors

Same shape as presence, but adds cursor: { x, y } updated on mousemove (debounced to 30 fps).

Pattern: notifications stream

A notifications table indexed by_user_and_read. Component subscribes via useQuery for the unread set. Reactive sync delivers new notifications instantly.

Limits

  • ~10,000 concurrent subscribers per query (soft).
  • Per-document presence with 1000+ users requires sharding or a different store.

Tips

Don’t store cursor positions in your main table. Put them in a separate cursors table; cleanup is much easier.
Start without OT. Most “collaboration” features (comments, presence, simultaneous form editing on different fields) work fine without OT.

Real-time sync

The basics.

Real-time chat recipe

A worked example.

Multiplayer game recipe

Heavier real-time use.
Last modified on April 18, 2026