GROQ query to find related posts by shared tags

10 replies
Last updated: Sep 26, 2021
Hi all, I am trying to find an example of a groq query that will return related posts based on tags for a blog post. Each post in my blog can have one or more tags. I would like to query for other posts that share one or more of the same tags so I can display links to them.
AI Update

Great question! Finding related posts by tags is a common pattern in blog setups. Here's how to approach this in GROQ when your tags are references:

Basic Approach - Matching Any Tag

If you want to find posts that share any tag with the current post, you can use this pattern:

*[
  _type == "post" 
  && _id == "your-post-id"
][0] {
  _id,
  title,
  tags,
  "related": *[
    _type == "post"
    && _id != ^._id
    && count(tags[@._ref in ^.^.tags[]._ref]) > 0
  ]
}

How It Works

The key technique here is using count(tags[@._ref in ^.^.tags[]._ref]) > 0 which:

  1. tags[@._ref in ^.^.tags[]._ref] - Filters the related post's tags to only those whose _ref appears in the parent post's tags array
  2. count(...) > 0 - Checks if there's at least one matching tag
  3. ^.^.tags[]._ref - The double ^ is needed because the array filter tags[query] creates its own closure, so you need to reference up two levels to get the parent post's tags
  4. _id != ^._id - Excludes the current post from results

For Multiple Posts at Once

If you're querying multiple posts and want related posts for each:

*[_type == "post"] {
  title,
  slug,
  excerpt,
  tags[]->{title},
  "related": *[
    _type == "post"
    && _id != ^._id
    && count(tags[@._ref in ^.^.tags[]._ref]) > 0
  ] {
    title,
    slug,
    excerpt
  }
}

If Tags Are Simple Strings

If your tags field is just an array of strings (not references), the query is simpler:

*[_type == "post" && count(tags[@ in ["tag1", "tag2"]]) > 0]

Testing Your Query

You can test and experiment with these queries live at groq.dev. The reference-based approach is more flexible since it lets you store additional metadata about tags (like descriptions, colors, etc.) in your tag documents.

This pattern is super useful for building "you might also like" sections or related content widgets on your blog posts!

Show original thread
10 replies
Checkout this example to match any array element in another array https://twitter.com/DerekNguyen10/status/1415498127927222273
Assuming your document has a
tags
field that is an array of string
*[count(tags[@ in ["tag1", "tag2"]]) > 0]
IIRC it can be done if your tags are references as well, but it’s a little more complicated
Thanks
user G
! My tags are references but this is a good start.
Struggling. I can do this so easily in WordPress. Completely stumped on how to return related posts with groq. Seems like I must be missing something.
user X
Here’s a better example, with references:
https://groq.dev/tDY6zeAeIVV8HV6vS2Wq3m

// Get all posts that references "Cat 1", "Cat 2", or both

*[
  _type == "post"
  && count(tags[@->title in ["Cat 1", "Cat 2"]]) > 0
 ]
I can give you a more detail query if you share your data structure. This is one of the few areas where GROQ get a little challenging, but I think it’s worth it!

Also I have to mention that while the example I share is pure GROQ, you can just use javascript to form a query that’s (kind of) more readable:


const cats = ["Cat 1", "Cat 2"]
const conditions = cats.map(catName => `references(*[_type == "cat" && title == "${catName}"]._id)`).join(" && ")

client.fetch(`
  *[_type == "post" && ${conditions}]
`)
Thanks
user G
I will see if I can wrap my head around this, much appreciated...
I have a post schema that has a 'tags' field which is an array of references to a tag document schema. The tag document has title and description fields defined.
I have added tags 'Tag 1', 'Tag 2', 'Tag 3' and assigned them to my test posts.

When I run the following query, related has 0 results.


*[_type == "post"]{

title,

slug,

excerpt,

mainBlocks,

tags[]->{title},

"related":  *[_type == "post" && count(tags[@->title in ["Tag 1", "Tag 2", "Tag 3"]]) > 0]{

title,

slug,

excerpt,

}

}
any further help gratefully received. This is fundamental stuff for me and if I can it working I can ditch WordPress.
Ohhh I forgot that you’re gonna do a sub query — In that case it’s a little trickier, however we get access to _ref for free so at the same time a little easier:
https://groq.dev/6sujs6C5EhYX1EdP1VCE64

*[
  _type == "post" && _id == "p5"
 ][0] {
   _id,
   tags,
   "related": *[
     _type == "post"
     && _id != ^._id
     && count(tags[@._ref in ^.^.tags[]._ref]) > 0
   ]
 }
A breakdown:
• So same trick as before -
count(arrayOne[@ in arrayTwo]) > 0
to check any items in an array for another• Instead of
arrayOne
, use the related posts’
tags
• Instead of
arrayTwo
, use the parent posts’
tags
. We can usually access the parent closure in sub queries by using
^.tags
:
*[_type == "post"][0] { "related": *[ _type == "post" && tags[0]._ref in ^.tags[]._ref] } // get posts that has the same first tag
• However,
tags[query]
seems to create another closure, so we have to double up:
^.^.tags
• Then we project the tags to get a simple array of strings:
tags[]._ref
->
["catId1", "catId2"]
Let me know if that works out!

edit: looking at your query, it should actually works, so perhaps the issue is somewhere else. Which API version are you on? and are you querying the same dataset as the one you use to set up the test?
Wow,
user G
you are very kind. I would like to buy you a beer (or a coffee). Thanks so much 🙏👍😁
user X
NP at all! I needed this as well & someone else here helped me figure this out. Just passing along. Did you get it working?
Yes I did. It works perfectly. You really helped me out. Thanks again Derek!

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?