Content Lake (Datastore)

Real-time updates

How API clients can listen for data changes in realtime.

Protip

The Sanity data store supports realtime updates, allowing API clients to listen for content changes. This is used for collaborative editing in our content studio, where your view of the document is updated as other people make changes. These updates are available to your own apps as well, and have a wide range of uses, such as:

  • Alerting end-users of breaking news stories.
  • Updating client state in a multiplayer game.
  • Transmitting chat messages between users.
  • Controlling IoT (Internet-of-Things) devices such as home automation systems.

Listeners use the Server-Sent Events protocol, by making an HTTPS request to:

https://<project-id>.api.sanity.io/v2026-05-21/data/listen/<dataset>?query=<GROQ-query>

The server will keep the connection open and stream events as they occur for any documents matching the provided GROQ query. Further parameters and details are listed in the listeners reference.

Gotcha

We recommend using one of our client libraries to listen for updates, which will automatically decode events into native data structures and handle stuff like automatic reconnects. Here's an example using our JavaScript library:

const query = '*[_type == "comment" && authorId != $ownerId]'
const params = {ownerId: 'myUserId'}

const subscription = client.listen(query, params)
  .subscribe(update => {
    const comment = update.result
    console.log(`${comment.author} commented: ${comment.text}`)
  })

Events

Protip

welcome

When the listener is set up and ready to serve mutations, you will receive the welcome event. It looks like this:

event: welcome
data: {"listenerName": "Ua6BR3GwQ14cnZXrgwCdsF"}

You don't need to process this event, but it could be used to kick off other processing. If you are tracking changes to keep a document in sync on the client side, this is a good time to fetch the initial document using the doc endpoint. Fetching the document after the listener is ready ensures that you receive every subsequent mutation. If you fetch the initial document before setting up the listener, you may miss one or more mutations in the intervening time.

mutation

The most common event is the mutation event, which looks like this:

event: mutation
id: lqgiok-skp-eja-k6z-9wrng7k5e#38123cba-286c-45a0-a6d1-3cc4dc43748a
data: <JSON-payload on a single line>

The payload is a single line of JSON. The listener reference has a complete list of fields and descriptions, but some of the most useful fields are summarized below:

  • documentId: the ID of the modified document
  • transition: type of event - update, appear, or disappear
  • identity: the user making the changes
  • mutations: an array of mutations as submitted to the mutate endpoint
  • result: the complete document after the mutations are applied
  • previousRev: the document revision ID before the mutation
  • resultRev: the document revision ID after the mutation
  • timestamp: time when the mutation was applied
  • visibility: whether the change is visible to queries yet (query), or only to subsequent transactions (transaction).

Gotcha

channelError

Errors during processing will appear as channelError events. These are typically caused by syntax errors in the query, and look like this:

event: channelError
message: {"message": <the error message>}

disconnect

Normally, if you are disconnected from a listener endpoint you should just immediately reconnect. However, if you receive the disconnect event, you should disconnect and stay away. Typically this means you just got a channelError that is considered fatal (e.g. a syntax error) and reconnecting will just repeat the ordeal. The event looks like this:

event: disconnect
data: {"reason": <a string describing the reason>}

Listeners in Sanity Studio

A single Studio session opens many listeners at once, not one. The preview system, Tasks, Releases, and each open document subscribe separately, so one browser tab produces a steady stream of requests to the listen endpoint.

These requests carry first-party request tags such as sanity.studio.preview.observe-document-set.listen, which is often the highest-volume tag in a project's request logs. See request tags.

Reconnect behavior

@sanity/client reconnects on its own after a transient connection failure, waiting one second before it retries. Dropped connections, 5xx responses, 408, and 429 all count as transient. A rejection it can identify as permanent, such as an expired token, is surfaced to your code as an error instead, so the client doesn't retry it in a loop.

Diagnose a spike of 401 responses

A Studio left open with stale credentials shows up in request logs as a large number of 401 responses on the listen endpoint, concentrated on a few IP addresses and often continuing outside working hours. The shape looks like an attack, but it is usually one client that can no longer authenticate, repeated across the many listeners that session holds open.

Before you treat that traffic as hostile, check for:

  • Orphaned browser tabs left open on a Studio.
  • Development instances still running against the project.
  • Studios hosted in a desktop shell such as Electron.

Signing fully out and back in clears the stale credentials. Remember that a request tag is set by the client, so a sanity.studio tag on those requests is not evidence that the caller signed in. For what the failed requests mean for your bill, see plans and payments.

Was this page helpful?