How to fetch tags from Sanity and count their usage in posts for front-end display
The key to solving this is using the ^ caret operator in GROQ, which allows you to reference the parent scope. Here's how to fix your query:
*[_type == "tag"] {
title,
slug,
_id,
"postCount": count(*[_type == "post" && references(^._id)])
}[postCount > 0] | order(title asc)The ^ operator lets you access the current tag document from within the nested query. So ^._id refers to the _id of the tag you're currently iterating over.
Even better, you can use the references() function which is specifically designed for this use case. It checks if any document references the given document ID.
Here's a breakdown of what's happening:
*[_type == "tag"]- Get all tags- For each tag, project the fields you want plus a
postCountfield count(*[_type == "post" && references(^._id)])- Count posts that reference this tag's ID[postCount > 0]- Filter to only tags with at least one post| order(title asc)- Sort by title
This approach is much cleaner than trying to match slug strings, and it works with Sanity's reference system directly. The references() function will check if the tag's _id appears in any reference field in the post documents (like your tags array).
If you specifically need to filter based on slug matching for some reason, you could do:
*[_type == "tag"] {
title,
slug,
_id,
"postCount": count(*[_type == "post" && ^.slug.current in tags[]->slug.current])
}[postCount > 0] | order(title asc)But the references() approach is more performant and idiomatic.
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.