Why is GROQ Query Returning an Empty Set in my App but not in the Vision Plugin?

12 replies
Last updated: Apr 7, 2022
hi — can anyone think of why my GROQ query would be returning an empty set in my app but not in the Vision Plugin? My query is
*[ _type == "exhibition" && (!isUpcoming || isUpcoming == null)]
. The query works in the client, returning “exhibitions” if I don’t include the “isUpcoming” conditionals. But then returns an empty set as soon as I add the conditional
AI Update

This is a classic perspective issue! The Vision plugin and your app client are likely querying with different perspectives, which means they're looking at different versions of your documents.

What's happening:

Your isUpcoming field might exist on draft documents but not on published ones (or vice versa). When you add the && (!isUpcoming || isUpcoming == null) filter, the query returns nothing because the documents your app is querying don't have that field in the expected state.

Key things to understand:

  1. Draft documents have IDs prefixed with drafts., so a single exhibition can exist as both a published document (exhibition-123) and a draft (drafts.exhibition-123) simultaneously.

  2. Different perspectives show different views:

    • published - Only published documents (no drafts)
    • drafts (formerly previewDrafts) - Prioritizes drafts over published when both exist
    • raw - Returns everything (both drafts and published)
  3. Important: With API version 2025-02-19+, the default perspective changed to published. This means both Vision and your client now default to published unless explicitly configured otherwise.

How to diagnose:

First, check what perspective your client is using:

const client = createClient({
  // ... other config
  perspective: 'published', // Check what's here (or if it's missing)
})

Then run this in Vision to see which documents have the isUpcoming field and whether they're drafts or published:

*[_type == "exhibition"]{
  _id, 
  isUpcoming, 
  "isDraft": _id in path("drafts.**")
}

This will show you whether isUpcoming exists on drafts, published docs, or both.

Solutions:

  1. Publish your documents: If isUpcoming only exists on drafts, publish those exhibition documents so the field appears in the published perspective.

  2. Use the drafts perspective: If you need to see unpublished changes in your app, configure your client to use the drafts perspective:

    const client = createClient({
      // ... other config
      useCdn: false, // must be false when using drafts perspective
      perspective: 'drafts',
    })
  3. Use the raw perspective: If you want to see all documents (both drafts and published) like the old default behavior:

    const client = createClient({
      // ... other config
      perspective: 'raw',
    })
  4. Test with explicit perspective in Vision: Try querying only published docs in Vision to confirm the issue:

    *[_type == "exhibition" && !(_id in path("drafts.**")) && (!isUpcoming || isUpcoming == null)]

Most likely, you need to either publish your exhibition documents so isUpcoming appears on published versions, or configure your client to use a perspective that includes the drafts where isUpcoming is actually set. The difference you're seeing is because Vision and your app may be defaulting to different perspectives, or you have unpublished changes that only exist in draft form.

Are they both using the same api version?
that’s a good question, how would I check this?
in Vision you select it at the top, in your client you specify it as an option, if not it defaults to v1 (I think).
It’ll be in your api request urls in your app (where you initiate the client if you’re using one https://github.com/sanity-io/client#api )
And in the vision plugin it should show near the top
Another thing is that perhaps one request happens authenticated, and the other un-authenticated, and that that influences drafts vs no drafts or something like that
okay thanks for the help! turns out I wasn’t explicitly stating the apiVersion and changing it to the one in Vision fixed the problem
It’ll be in your api request urls in your app (where you initiate the client if you’re using one https://github.com/sanity-io/client#api )
And in the vision plugin it should show near the top
that’s a good question, how would I check this?
Are they both using the same api version?
Another thing is that perhaps one request happens authenticated, and the other un-authenticated, and that that influences drafts vs no drafts or something like that
okay thanks for the help! turns out I wasn’t explicitly stating the apiVersion and changing it to the one in Vision fixed the problem
in Vision you select it at the top, in your client you specify it as an option, if not it defaults to v1 (I think).

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?