GROQ query `isArchived != true` not filtering archived posts correctly

13 replies
Last updated: Dec 1, 2025
So I am running into what I think is an odd issue where my query looks like this
export const allPosts = groq`*[_type == "post" && isArchived != true]{
  ${postFields}
}`
As when I inspect my post I see
isArchived:true
and I don't want to display posts that are listed as archived. However, this still shows that post. If I update the query to
export const allPosts = groq`*[_type == "post" && isArchived == false]{
  ${postFields}
}`
or even

export const allPosts = groq`*[_type == "post" && !isArchived]{
  ${postFields}
}`
the posts are hidden that are listed as archived
AI Update

This is a common gotcha with GROQ's inequality operator! The issue you're experiencing comes down to how GROQ handles null values in filter expressions.

When you use isArchived != true, here's what happens:

  • If isArchived is false, the expression evaluates to true ✓ (document matches)
  • If isArchived is true, the expression evaluates to false (document is filtered out)
  • If isArchived is null or undefined, the expression evaluates to null

The key problem is that when a filter expression returns null, GROQ treats it as falsy and filters out that document. However, the behavior can be inconsistent or confusing because null in boolean contexts doesn't always behave as you'd expect, and the != operator specifically returns null when comparing with null values.

So isArchived != true should theoretically work, but the way null is handled in filter contexts can lead to unexpected results. This is why your working solutions are more reliable:

Option 1: Explicit false check

*[_type == "post" && isArchived == false]

This explicitly matches documents where isArchived is exactly false. Documents where the field is null or undefined will NOT match (because null == false returns null, which is falsy in filters).

Option 2: Negation operator (recommended)

*[_type == "post" && !isArchived]

This is the most idiomatic approach in GROQ. The ! operator will evaluate to true for false, null, and undefined values, so it includes all non-archived posts regardless of whether the field exists.

Option 3: Explicit null handling If you want to be very specific about which documents to include, you can use the defined() function:

*[_type == "post" && (!defined(isArchived) || isArchived == false)]

This explicitly includes posts where isArchived doesn't exist OR is false.

For most use cases with a boolean "archived" flag, !isArchived is your best bet - it's clean, readable, and treats missing values the same as false, which is typically what you want for feature flags like this.

Show original thread
13 replies

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?