Skip to main content
Sanity Studio supports real-time collaboration, allowing multiple users to work on the same content simultaneously with live updates and presence indicators.

How real-time collaboration works

Sanity’s real-time collaboration is built on three core concepts:
  1. Presence: See which users are viewing or editing documents
  2. Live mutations: Changes sync across all connected clients
  3. Optimistic updates: UI updates immediately while syncing in background

Architecture overview

The collaboration system uses:
  • WebSocket connections for real-time event streaming
  • Mutation system for applying changes
  • Document store for managing subscriptions
  • Event system for change propagation
┌─────────────────────────────────────────────────────────────┐
│                       SANITY STUDIO                          │
│                                                              │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐  │
│  │   User A     │    │   User B     │    │   User C     │  │
│  │   Editing    │    │   Viewing    │    │   Editing    │  │
│  └──────┬───────┘    └──────┬───────┘    └──────┬───────┘  │
│         │                   │                   │           │
│         └───────────────────┼───────────────────┘           │
│                             │                               │
│                    ┌────────▼────────┐                      │
│                    │  Document Store │                      │
│                    │  + Presence     │                      │
│                    └────────┬────────┘                      │
│                             │                               │
└─────────────────────────────┼───────────────────────────────┘

                     ┌────────▼────────┐
                     │  Content Lake   │
                     │   (WebSocket)   │
                     └─────────────────┘

Presence system

Presence shows who is currently viewing or editing a document.

How presence works

  • Each user’s session broadcasts their location and activity
  • Presence is shown in the document header and on specific fields
  • Presence indicators update in real-time as users navigate

Viewing presence in the UI

The studio automatically shows:
  • User avatars in the document toolbar for all viewers
  • Field-level indicators when someone is editing a specific field
  • Typing indicators for text fields being actively edited

Live mutations

Mutations are changes to documents that sync in real-time.

Mutation flow

1
User makes a change
2
When a user edits a field, the form generates a patch:
3
{
  type: 'set',
  path: ['title'],
  value: 'New Title'
}
4
Patch applied optimistically
5
The UI updates immediately before server confirmation.
6
Mutation sent to Content Lake
7
The patch is sent to the backend via the document store.
8
Other clients receive the mutation
9
Connected clients receive the change through WebSocket and update their UI.

The mutator package

The @sanity/mutator package handles mutation logic:
  • Applies patches to documents
  • Handles conflicts when multiple users edit the same field
  • Validates mutations before applying
  • Maintains document integrity during concurrent edits

Conflict resolution

When multiple users edit the same field:
  1. Last write wins: The most recent change takes precedence
  2. Field-level granularity: Different fields can be edited simultaneously
  3. Optimistic UI: Each user sees their changes immediately
  4. Server reconciliation: The server determines the final state
Sanity uses operational transformation principles to handle concurrent edits gracefully.

Real-time updates in custom components

You can subscribe to document changes in your components:
import {useEffect, useState} from 'react'
import {useClient} from 'sanity'

function LiveDocumentViewer({documentId}: {documentId: string}) {
  const client = useClient({apiVersion: '2025-02-07'})
  const [document, setDocument] = useState(null)
  
  useEffect(() => {
    // Initial fetch
    client.getDocument(documentId).then(setDocument)
    
    // Subscribe to changes
    const subscription = client
      .listen(`*[_id == $id]`, {id: documentId})
      .subscribe((update) => {
        if (update.type === 'mutation') {
          // Refetch or apply mutation
          client.getDocument(documentId).then(setDocument)
        }
      })
    
    return () => subscription.unsubscribe()
  }, [client, documentId])
  
  return (
    <div>
      <h1>{document?.title}</h1>
      {/* render document */}
    </div>
  )
}

Document store integration

The document store manages real-time subscriptions:
import {useDocumentStore} from 'sanity'

function MyComponent({documentId}: {documentId: string}) {
  const documentStore = useDocumentStore()
  
  useEffect(() => {
    const subscription = documentStore
      .listenQuery(`*[_id == $id]`, {id: documentId})
      .subscribe((event) => {
        console.log('Document updated:', event)
      })
    
    return () => subscription.unsubscribe()
  }, [documentStore, documentId])
}

Enabling comments

Sanity supports inline comments for collaboration:
import {defineConfig} from 'sanity'

export default defineConfig({
  document: {
    comments: {
      enabled: true,
    },
  },
})
With comments enabled:
  • Users can leave feedback on specific fields
  • Comments appear in context
  • Thread discussions in real-time
  • Resolve comments when addressed

Collaborative workflows

Draft and published states

Sanity maintains separate draft and published versions:
  • Draft: Working copy with drafts. prefix
  • Published: Live version without prefix
  • Collaboration happens on drafts: Multiple users edit the draft
  • Publishing is atomic: Draft replaces published in one operation
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Draft     │────▶│  Published  │────▶│  Historical │
│  (working)  │     │   (live)    │     │  (versions) │
└─────────────┘     └─────────────┘     └─────────────┘


  Multiple                                     
   users                                       
  editing                                      

Version history

All changes are tracked:
  • View document history in the studio
  • Restore previous versions
  • Compare changes between versions
  • See who made each change

Optimistic updates

The studio uses optimistic updates for instant feedback:
import {set, useFormBuilder} from 'sanity'

function OptimisticInput(props) {
  const {onChange, value} = props
  
  const handleChange = (newValue: string) => {
    // UI updates immediately
    onChange(set(newValue))
    
    // Sync happens in background
  }
  
  return (
    <input
      value={value || ''}
      onChange={(e) => handleChange(e.target.value)}
    />
  )
}

Network handling

The studio gracefully handles network issues:
  • Offline editing: Changes queue locally
  • Reconnection: Queued changes sync when online
  • Conflict resolution: Server reconciles conflicting changes
  • Status indicators: Shows connection state in UI

Rate limiting and throttling

To ensure performance:
  • Debounced updates: Rapid changes are batched
  • Throttled presence: Presence updates are rate-limited
  • Efficient subscriptions: Only active documents are monitored

Best practices for collaboration

Structure your schema to minimize conflicts:
  • Use granular fields instead of large text blocks
  • Separate frequently edited fields
  • Consider using arrays for multi-user content
  • Enable comments for async feedback
  • Use presence indicators to avoid conflicts
  • Establish workflows for content approval
  • Open multiple browser windows
  • Simulate concurrent edits
  • Verify changes sync correctly

Monitoring collaboration

You can monitor collaboration activity:
import {useDocumentPresence} from 'sanity'

function DocumentPresenceIndicator({documentId}: {documentId: string}) {
  const presence = useDocumentPresence(documentId)
  
  return (
    <div>
      {presence.length} user(s) viewing this document
    </div>
  )
}

Disabling real-time features

If needed, you can disable certain features:
export default defineConfig({
  document: {
    // Disable comments
    comments: {
      enabled: false,
    },
  },
})
Real-time sync cannot be disabled as it’s core to Sanity’s architecture.

Next steps

Build docs developers (and LLMs) love