Content

Real-time

CellCMS provides real-time features via WebSocket: presence indicators (see who's editing), live query subscriptions (get notified when content changes), and mutation broadcasts.

Connecting

Connect to the WebSocket endpoint with a JWT token and dataset:

wss://api.cellcms.com/ws?token=YOUR_JWT&dataset=production

Welcome Message

On successful connection, the server sends a welcome message:

{
  "type": "welcome",
  "clientId": "unique-client-id",
  "userId": "user-uuid"
}

Presence Snapshot

Immediately after the welcome, you receive a snapshot of all users currently editing documents:

{
  "type": "presence.snapshot",
  "documents": [
    {
      "documentId": "doc-uuid-1",
      "users": [
        { "userId": "user-1", "userName": "Alice" },
        { "userId": "user-2", "userName": "Bob" }
      ]
    }
  ]
}

Authentication Errors

If the token is missing or invalid, the connection is closed with code 4001.


Presence

Presence tracking shows which users are actively editing which documents. This powers the avatar indicators in the Studio editor.

Join a Document

When a user starts editing a document, send:

{
  "type": "presence.join",
  "documentId": "doc-uuid"
}

All other connected clients receive:

{
  "type": "presence.join",
  "documentId": "doc-uuid",
  "userId": "user-uuid",
  "userName": "Alice"
}

Leave a Document

When a user stops editing:

{
  "type": "presence.leave",
  "documentId": "doc-uuid"
}

All other clients receive:

{
  "type": "presence.leave",
  "documentId": "doc-uuid",
  "userId": "user-uuid",
  "userName": "Alice"
}

Automatic Cleanup

When a WebSocket connection is closed (user closes the tab, network loss, etc.), the server automatically removes that user from all document presence lists and broadcasts the appropriate leave messages.


Live Query Subscriptions

Subscribe to real-time updates matching a GROQ query pattern. When documents matching your subscription are created, updated, or deleted, you receive a notification.

Subscribe

{
  "type": "subscribe",
  "query": "*[_type == \"post\"]",
  "dataset": "production"
}

Server confirms:

{
  "type": "subscribed",
  "query": "*[_type == \"post\"]"
}

Mutation Events

When a matching document changes, all subscribers receive:

{
  "type": "mutation",
  "documentId": "doc-uuid",
  "documentType": "post",
  "operation": "update",
  "ts": "2025-01-15T12:00:00.000Z"
}

Operations: create, update, delete, publish, unpublish

Document Updates

When a document you're tracking via presence is modified:

{
  "type": "document.updated",
  "documentId": "doc-uuid",
  "operation": "update",
  "ts": "2025-01-15T12:00:00.000Z"
}

Unsubscribe

{
  "type": "unsubscribe",
  "query": "*[_type == \"post\"]"
}

Server confirms:

{
  "type": "unsubscribed",
  "query": "*[_type == \"post\"]"
}

Client SDK Usage

The @cellcms/client SDK provides a listen() method for subscribing to real-time updates:

import { createClient } from '@cellcms/client'

const client = createClient({
  apiUrl: 'https://api.cellcms.com',
  project: 'default',
  dataset: 'production',
  token: 'YOUR_TOKEN',
})

// Listen for changes to all blog posts
const subscription = client.listen('*[_type == "post"]')

subscription.subscribe((event) => {
  console.log('Document changed:', event.documentId)
  console.log('Operation:', event.operation) // create, update, delete, publish, unpublish
  console.log('Timestamp:', event.ts)

  // Re-fetch the updated data
  // ...
})

Event Type

interface ListenEvent {
  type: 'mutation'
  documentId: string
  operation: 'create' | 'update' | 'delete' | 'publish' | 'unpublish'
  ts: string
}

Message Reference

Client → Server

Message TypeFieldsDescription
presence.joindocumentIdStart tracking presence on a document
presence.leavedocumentIdStop tracking presence on a document
subscribequery, dataset?Subscribe to mutation events
unsubscribequeryUnsubscribe from mutation events

Server → Client

Message TypeFieldsDescription
welcomeclientId, userIdConnection established
presence.snapshotdocuments[]Current presence state for all documents
presence.joindocumentId, userId, userNameUser started editing
presence.leavedocumentId, userId, userNameUser stopped editing
subscribedquerySubscription confirmed
unsubscribedqueryUnsubscription confirmed
mutationdocumentId, documentType, operation, tsDocument changed
document.updateddocumentId, operation, tsTracked document changed

Dataset Isolation

WebSocket connections are scoped to a single dataset (specified in the connection URL). Presence and mutation events from other datasets are not visible.


Studio Real-time Features

The Studio uses WebSocket for:

  • Presence avatars: Colored circles in the editor toolbar showing who else is editing the current document
  • Connection indicator: Green dot in the top bar when WebSocket is connected
  • Live updates: Document list refreshes automatically when documents are created, updated, or deleted by other users