How to fetch tags from Sanity and count their usage in posts for front-end display

4 replies
Last updated: Jun 4, 2023
I'm trying to get all of my tags from Sanity but also count how many posts use it so I can know if I need to display the tag on the front-end or not. Currently using:

'tags': *[_type == "tag"] | order(title asc){
      title,
      slug,
      _id,
      "references": *[count((_type == "post" && tags[]->slug.current)[@ == <The current tag's slug>]) > 0]
    }
As you can see, I'm struggling with how to reference the current tag's slug in the query.

The desired outcome is that I only fetch tags that have been used by other content, so I'm not fetching un-needed data or displaying a tag with no content on the front-end.
AI Update

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:

  1. *[_type == "tag"] - Get all tags
  2. For each tag, project the fields you want plus a postCount field
  3. count(*[_type == "post" && references(^._id)]) - Count posts that reference this tag's ID
  4. [postCount > 0] - Filter to only tags with at least one post
  5. | 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.

You should be able to access the current slug using:
^.slug.current
Thanks! Still not working and only returning an empty array, but I'll work it out. Cheers for the response
I think you may be complicating the references projection, since you need to compare a single slug to and array of slugs. Can you try:
*[_type == "post" && ^.slug.current in tags[]->slug.current]

This is awesome. 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?