Filter out current document in GROQ related posts query

2 replies
Last updated: Jun 12, 2023
Still learning how to write more advanced custom GROQ queries and I'm using this to return all related documents based on tags:
"related": *[_type == "post" && count(categories[@._ref in ^.^.categories[]._ref]) > 0] | order(publishedAt desc, _createdAt desc) [0..5] {
     title,
     slug
   }
(as found here:
https://www.sanity.io/schemas/get-related-items-of-a-post-in-sanity-by-comparing-category-array-reference-with-another-array-0d752dd7 )
However, this returns the current document again, as it's got the same tags. How can I adjust this to filter out the current document? (something like
slug.current != slug
but this doesn't take into consideration what the ^ and @ operators do (still learning this).
AI Update

Perfect! You're on the right track. To exclude the current document from your related posts query, you need to filter out documents where the _id matches the parent document's _id.

Here's how to adjust your query:

"related": *[
  _type == "post" 
  && count(categories[@._ref in ^.^.categories[]._ref]) > 0 
  && _id != ^.^._id
] | order(publishedAt desc, _createdAt desc) [0..5] {
  title,
  slug
}

The key addition is && _id != ^.^._id. Let me break down what's happening:

  • ^.^._id uses the parent operator to traverse up two levels to access the _id of the original document you're querying
  • The @ operator in categories[@._ref in ^.^.categories[]._ref] represents each item in the current document's categories array as it's being evaluated
  • The double ^.^ is needed because you're inside a projection (the {} part) which is itself nested inside the filter

So the filter reads: "Give me posts that share categories with the parent document, but exclude the parent document itself by checking that _id doesn't match."

This is a common pattern when building "related content" features, and comparing _id values is the most reliable way to exclude the current document since _id is unique across all documents.

Show original thread
2 replies
The caret (
^
) is the parent operator, which means it goes one level up in scope. A filter boils down to one or more booleans, returning every document that matches them all. By adding an additional check to your “related” filter that ensures the document doesn’t match the parent, you can avoid returning the current document again. Something like
_id != ^._id
should work.

"related": *[_type == "post" && count(categories[@._ref in ^.^.categories[]._ref]) > 0 && _id != ^._id] // ...
Here,
_id
is in the context of the “related” filter, while
^._id
is in the context of the parent (not shown in the original post, but implied to exist).
This is perfect. Brilliant explanation. I've used the
^
operator once, but not fully grasped it. This is great. Thanks!

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?