Sanity listen() receiving all document changes instead of filtered single document

9 replies
Last updated: May 24, 2021
Trying to listen to changes on single document, getting any changed document
Using the
Sanity JS client.listen method, we’re trying to listen to changes on a single document but are receiving any changed document, not just the intended target with matched field as specified in the GROQ filter.
We’ve tried signatures both with a without the “params” parameter, but are getting the same issue with both of the following query setups that attempt to listen only to the ‘show’ with matching ‘slug’ field:

Query only:

const showQuery = `*[_type == "show" && slug == "${this.slug}"][0]{field1, field2, ...}`;
sanity.client.listen(showQuery);
Query and params:

const showQuery = `*[_type == "show" && slug == $slug][0]{field1, field2, ...}`;
const showParams = { slug: `${this.slug}` };
sanity.client.listen(showQuery, showParams);
Both approaches fire updates with any changed document.

Can anyone help with tips/direction on how to restrict listener updates to a single intended record? Thanks!
AI Update

You've hit a common gotcha with Sanity's client.listen() method! The issue is that listener queries only use the top-level filter portion of your query - projections, slicing (like [0]), and other operations are ignored.

According to the Listen API documentation:

"Only the top-level filter portion of the query will be used, anything else including projection, subqueries, slicing or aggregation will be ignored."

So when you write:

const showQuery = `*[_type == "show" && slug == "${this.slug}"][0]{field1, field2, ...}`;

The listener is only seeing *[_type == "show" && slug == "${this.slug}"] and ignoring the [0] slice and the projection. This means you'll get events for all documents matching that filter.

The fix: Remove the [0] and projection from your listen query. Your filter is actually working correctly - you just need to handle the filtering on the client side:

const showQuery = `*[_type == "show" && slug.current == $slug]`;
const showParams = { slug: this.slug };

const subscription = client.listen(showQuery, showParams)
  .subscribe(update => {
    // The update.result will contain the full document after mutations
    const show = update.result;
    console.log('Show updated:', show);
  });

Important notes:

  1. Use slug.current if your slug field is a slug type (which has a current property). If it's just a string field, then slug is fine.

  2. Always use params for dynamic values to avoid injection issues and ensure proper encoding:

    // Good ✅
    const query = `*[_type == "show" && slug.current == $slug]`;
    const params = { slug: this.slug };
    
    // Avoid ❌
    const query = `*[_type == "show" && slug == "${this.slug}"]`;
  3. The update object from the listener includes useful fields like:

    • update.result - the complete document after changes
    • update.transition - type of event (update, appear, disappear)
    • update.mutations - the actual mutations applied
  4. If you need real-time updates for your frontend application, you might also want to check out the newer Live Content API, which is more efficient for keeping content in sync with frameworks like Next.js, Nuxt, and others.

Show original thread
9 replies
Listening to a single document by querying just on the ID does appear to work, ala:

const observerQuery = `*[_id == $id]`; 
But still not clear why other queries like the above mentioned that filtered on fields besides ID would not work in the same way, or would catch changes on non-matching documents.
It appears using the slice operation
[0]
in a listen query will cause it to fail to listen to a single desired document and instead catch all updates. Dropping the slice seems to be working…
If true, this seems like a good “gotcha” to call out in the official documentation.
I believe this is the intended functionality for listen queries as of now. I don't think it's a gotcha as it is mentioned here with regard to the listening queries.

https://www.sanity.io/docs/listening#query-65c01f82db74
Only the filter portion will work. I don't think you can do object projections with listen queries as of now. Which is essentially what happens when you add the
[0]
in your query.
The “gotcha” issue I think, from my perspective is that the slice isn’t simply ignored, but rather that the inclusion of the slice will alter functionality--the filter will no longer be respected, and you will receive update results that are unrelated/do not match the filter criteria.
Are you using the latest API versions?
Not 100% sure. Using @sanity/client v2.7.1, query XHR GETs look like:
https://XXXXXX.api.sanity.io/v1/data/listen/production?query= ….
(useCDN set to false for this specific use case)
sorry, that is partially correct, we are using @nuxtjs/sanity@0.7.1, which I believe is in turn using @sanity/client v2.7.1
Your sanity config should have the
apiVersion
key with the latest api. Sounds like your getting the old API bugs.Definitely upgrade your packages, Some pretty significant fixes since. I use Nuxt as well so I know that the latest version is around
.0.9.4
. Might fix up some of your issues.
Thanks for the advice, will look into locking down the apiVersion (I believe we’ve currently left it undefined) and updating packages.

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?