How to filter a GROQ query based on the presence of a specific field in the schema.

8 replies
Last updated: Jun 1, 2023
I have this groq query where I query all the pages and posts.

*[
  _type in ['page', 'post']
  && !(_id in path('drafts.**'))
  && dateTime(publishedAt) < dateTime(now()) {
   ....
  }
] 
I only want the posts which
publishedAt < now
. However, the
page
type doesn’t have the
publishedAt
field. If I add that condition, I will get no
page

How could I only apply the filter if the doc has publishedAt?
May 31, 2023, 11:19 AM
Could you not extend the third condition to be
type
= page OR
publishedAt < now
?
Something like

*[
  _type in ['page', 'post']
  && !(_id in path('drafts.**'))
  && (_type == 'page' || dateTime(publishedAt) < dateTime(now())) {
   ....
  }
] 
May 31, 2023, 11:39 AM
user Z
I think it works, why can’t we use
_type == 'post' && dateTime(publishedAt) < dateTime(now())


*[
  _type in ['page', 'post']
  && !(_id in path('drafts.**'))
  && (_type == 'post' && dateTime(publishedAt) < dateTime(now())) {
   ....
  }
] 
May 31, 2023, 11:59 AM
Or is any way to express “only apply the condition when there is a publishedAt in the schema”
May 31, 2023, 12:00 PM
I’m using API version “v2021-10-21”
May 31, 2023, 12:01 PM
I don't know if there's a way to express "only apply the condition when there's a publishedAt in the schema" but there is the
defined()
property which returns
true
if the argument is non-
null
, otherwise
false
.
So you could write the third condition as something like:

...
&& (!defined(publishedAt) || defined(publishedAt) && dateTime(publishedAt) < dateTime(now()))
...
May 31, 2023, 12:07 PM
There might be a better way.. but that's what I can think of
May 31, 2023, 12:07 PM
Great suggestions, (Removed Name)! Off to a running start!

user N
There are several different ways you could approach this. I like (Removed Name)’s approach because it keeps things very readable and easy to parse—and yours (using
_type == 'post' &&
instead of
_type == 'page' ||
) should work just as well. The reason this may not be working for you is because in the examples above, the filter isn’t ended before the projection (
{ … }
), so be sure to correct for that if needed.
Another option is to use one of GROQ’s functions to test for
publishedAt
, which removes the need for the second
_type
check. The
select
and
coalesce
functions are two possibilities, and both rely on the fact that a GROQ query’s filter essentially boils down to
true
or
false
. We could use `select`:

*[
  _type in ['page', 'post']
  && !(_id in path('drafts.**'))
  && select(
    publishedAt != null => dateTime(publishedAt) < dateTime(now()),
    true
  )
]{
 ...
}
This function works much like a conditional, where we test if
publishedAt
is not null, and if it’s not, we return
dateTime(publishedAt) < dateTime(now())
to the filter to be executed. If
publishedAt
is null, which you say will be the case for all
page
documents, then we just return
true
so that it doesn’t impact the filter.
Perhaps a bit more concise is `coalesce`:


*[
  _type in ['page', 'post']
  && !(_id in path('drafts.**'))
  && coalesce(
    dateTime(publishedAt) < dateTime(now()),
    true
  )
]{
 ...
}
This function returns the first non-null value. Thankfully, GROQ will resolve
dateTime(null)
to
null
, and when that’s compared against
dateTime(now())
, it returns null. Like in the
select
example, this will return
true | false
if
publishedAt
is non-null, or
true
if it is null.
I might have something wrong here, but hopefully it can get you started.
May 31, 2023, 8:23 PM
Thank you both for the answers 🎉 You made my day
Jun 1, 2023, 10:13 AM

Sanity– build remarkable experiences at scale

The Sanity Composable Content Cloud is the headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?

Categorized in