Issue with typegen and boolean filtering in qroq queries

9 replies
Last updated: Apr 24, 2024
I’m encountering a strange behavior with the typegen. A qroq query like this (where root is a boolean):
*[_type == 'page' && root != true]
generates the following:

pages: Array<never>
To fix this, I have to explicitly filter by root == false, but this doesn’t account for page where the boolean is not set.
AI Update

This is a known issue that was fixed in the Sanity CLI's type evaluator in April 2024. The problem occurs when using != true with boolean fields in GROQ queries - the typegen incorrectly infers Array<never> because it doesn't properly handle the case where boolean fields can be true, false, or undefined.

The issue you're experiencing is that in GROQ, root != true should match documents where root is either false or undefined (not set), but the type evaluator was incorrectly narrowing this to an impossible type (never).

According to the discussion on Sanity Answers, this was fixed and released in late April 2024. To get the fix:

  1. Update your Sanity CLI: Delete your lock file (package-lock.json, yarn.lock, or pnpm-lock.yaml) and reinstall dependencies to pick up the latest version of the CLI with the fixed type evaluator.

  2. Regenerate types: Run npx sanity typegen generate after updating.

Understanding GROQ Boolean Behavior

In GROQ, when filtering booleans:

  • root == true matches only documents where root is explicitly true
  • root == false matches only documents where root is explicitly false
  • root != true matches documents where root is false OR undefined/null
  • !root matches documents where root is false, undefined, or null

So your original query *[_type == 'page' && root != true] is actually the correct approach for finding pages that aren't root pages, including those where the field isn't set. The typegen just wasn't handling it properly before the fix.

If you're still experiencing this issue after updating, make sure you're using a recent version of the Sanity CLI (v3.37.0 or later should include the fix). The fix was confirmed by Sindre Gulseth from the Sanity team, who mentioned that deleting the lock file would pick up the latest version as it was released as a minor version update.

Hey! hmmm, I can't seem reproduce this behavior. Do you have a schema type called
page
?
Yes I do
import { defineField, defineType } from 'sanity'

export default defineType({
  title: 'Page',
  name: 'page',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      type: 'string'
    }),
    defineField({
      name: 'slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96
      }
    }),
    defineField({
      name: 'root',
      type: 'boolean'
    }),
    defineField({
      name: 'modules',
      type: 'array',
      of: [{ type: 'moduleText' }]
    })
  ]
})
My rootQuery looks like this:
export const rootQuery = groq`{
  'languages': *[_type == 'language'],
  'tags': *[_type == 'tag'],
  'pages': *[_type == 'page' && root != true]
}`
Ah. I can reproduce it
The generated RootQueryResult
export type RootQueryResult = {
  languages: Array<{
    id: string | null;
    title: string | null;
  }>;
  scripts: Array<{
    _id: string;
    title: string | null;
    slug: string | null;
  }>;
  tags: Array<{
    _id: string;
    title: string | null;
    slug: string | null;
  }>;
  pages: Array<never>;
};
I've reproduced it, and got a fix lined up - not sure when we'll get it out, but hopefully we'll get it sorted next week
Great to hear and thank you for looking into it so quickly.
Hi! Sorry it took some time, but this should now be fixed in our type evaluator, it will be part of next weeks release of our sanity cli.To test it now you can delete your lock file, and it should pick up the latest version as it's a minor release
Thanks for the update Sindre 🙌

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?