Filtering an array of linked events based on date in a Groq query

13 replies
Last updated: Jan 6, 2023
Hey, hoping someone could give me a hand with filtering a projection?
I have an array of linked events that need to disappear after their date has passed, and I could do this in the frontend but would quite like to do it as part of my query instead.
I know this isn't valid groq, but I essentially want to do the following (where
selected
is my array of events)
*[_type == 'page' ]{
    whatsOn {'results': selected[endDate ? (endDate >= '2022-01-06') : (date >= '2022-01-06')]->},
  } | order(_updatedAt desc)[0]
Some events have an
endDate
but others just have a
date
, then I want to only return the ones where this date is in the future. Does that make sense?
AI Update

You can definitely do this in GROQ! The key is to filter the array after dereferencing. You'll want to use a projection with array filtering syntax on the dereferenced objects.

Here's how to structure your query:

*[_type == 'page'] {
  whatsOn {
    'results': selected[]->[@.endDate >= '2022-01-06' || @.date >= '2022-01-06']
  }
} | order(_updatedAt desc)[0]

Let me break down what's happening:

  1. selected[]-> dereferences all items in your selected array
  2. [@.endDate >= '2022-01-06' || @.date >= '2022-01-06'] filters the dereferenced array
  3. The @ refers to the current element in the array being filtered
  4. The || (OR) operator checks either condition

However, if you want to handle the conditional logic more explicitly (checking endDate first if it exists, otherwise falling back to date), you can use the defined() function:

*[_type == 'page'] {
  whatsOn {
    'results': selected[]->[@.endDate >= '2022-01-06' || (!defined(@.endDate) && @.date >= '2022-01-06')]
  }
} | order(_updatedAt desc)[0]

This ensures that if endDate exists and passes the date check, it includes the event. If endDate doesn't exist, it falls back to checking date.

Or, for a cleaner approach using the coalesce operator:

*[_type == 'page'] {
  whatsOn {
    'results': selected[]->[@.endDate >= '2022-01-06' || coalesce(@.endDate, @.date) >= '2022-01-06']
  }
} | order(_updatedAt desc)[0]

The coalesce() function returns the first non-null value, so it'll use endDate if it exists, otherwise date.

Pro tip: Instead of hardcoding the date, you can use the now() function to always compare against the current date:

*[_type == 'page'] {
  whatsOn {
    'results': selected[]->[@.endDate >= now() || coalesce(@.endDate, @.date) >= now()]
  }
} | order(_updatedAt desc)[0]

This way your query automatically filters out past events without needing to update the date string! The pipeline components documentation has more details on how filtering works within projections.

You can use a simple boolean expression:
(endDate != null && endDate >= '2022-01-06') || (endDate == null && date >= '2022-01-06')
But this can probably be simplified to just:

coalesce(endDate, date) >= '2022-01-06`
Thanks
user L
, how can I tell it not to return from the projection though?
*[_type == 'pageChristmas' ]{
    whatsOn {'results': selected[coalesce(endDate, date) >= '2022-01-06']->{title}},
  } | order(_updatedAt desc)[0]
Returns 0 results, when it should return one
What do you mean by “not return”? If it’s returning no results, then something is wrong with the filter expression. 🙂
I noticed you use
->
. So is
selected
actually an array of refs? If so, you need to put the arrow here instead:
selected[]->[coalesce(endDate, date) >= '2022-01-06`] { title }
          ^^
Sorry for not being clear, yes this is an array of references to events elsewhere on my site. As the event ends, I want it to automatically hide from the page.Unfortunately this:

*[_type == 'pageChristmas' ]{
    whatsOn {'results':selected[]->[coalesce(endDate, date) >= '2022-01-06']},
  } | order(_updatedAt desc)[0]
Just returns null for each result?
Can you tell me what
selected[]->
returns? With no projection or filtering.
Of course, so its within an object called whatsOn, which has all of this content
Got it, so what happens if you just do
selected[]->
(no projection or filter after)?
Sorry i have just realised that I put 2022 earlier, I just got the new year wrong haha, i just want them to be in the future so should be:
*[_type == 'pageChristmas' ]{
    whatsOn {'results':selected[]->[coalesce(endDate, date) >= '2023-01-06']},
  } | order(_updatedAt desc)[0]
and there are definetly some with a date in the future
Sorry, my fault. You need to wrap the array expression in parantheses:
(selected[]->)[coalesce(endDate, date) >= '2023-01-06']

ah perfect!! That worked!
Thank you so much for your help!
Awesome!

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?