How to filter Sanity documents by referenced document's field value?

9 replies
Last updated: Feb 9, 2021
Hi all, i'm a little confused as to how to write a query that filters by the value of a referenced document. In my case, I have an
article
document type, within it an array of references to
tag
documents.
tag
docs have a unique slug. I'd like to get all articles that are tagged with
tag-a
and
tag-b
.
If I hard-code the refs for these tags, I can do it this way:


*[
  _type == "article"
  && tags[]._ref in ["518a1ceb-b5f2-4227-bfa2-42a39c723189", "3b2e74c0-7163-43db-a9d1-0fcf80969272"]
]{
  ...
}
But, I'm not sure how to do this by querying for the tag slug. Something like this:


*[
  _type == "article"
  && tags[]._ref in [*[_type == 'tag' && slug in ['tag-a', 'tag-b']]._id]
]{
  ...
}
AI Update

Good news - your approach is on the right track! GROQ does support filtering by referenced document fields, though the syntax needs a slight adjustment.

The issue with your second query is that you're trying to use a subquery directly inside the in operator. Instead, you need to construct an array of tag IDs first. Here are a few ways to accomplish this:

Option 1: Using a subquery with array construction

*[
  _type == "article"
  && count((tags[]._ref)[@ in *[_type == 'tag' && slug.current in ['tag-a', 'tag-b']]._id]) > 0
]

This checks if any of the article's tag references exist in the array of matching tag IDs.

This is often the cleanest approach - dereference the tags and filter on their properties directly:

*[
  _type == "article"
  && "tag-a" in tags[]->slug.current
  && "tag-b" in tags[]->slug.current
]

This dereferences each tag reference with -> and checks if the slug values match. Note that this requires both tags to be present (AND logic).

Option 3: For OR logic (articles with tag-a OR tag-b)

If you want articles that have either tag:

*[
  _type == "article"
  && count(tags[]->slug.current[@ in ['tag-a', 'tag-b']]) > 0
]

This dereferences the tags, gets their slugs, filters for matches, and checks if any exist.

The key insight is that the -> operator resolves references in the forward direction, letting you access fields on the referenced document directly. This is generally more readable and performant than constructing separate subqueries.

Keep in mind that dereferencing with -> does add some query complexity, but for most use cases the performance is perfectly acceptable. If you're working with very large datasets, you might want to check out query optimization techniques for additional performance improvements.

Show original thread
9 replies
Hi Joseph. This might need some work but could you give it a try?

*[_type == "article" && tags[]->slug.current match ["tag-a", "tag-b"]]
Thanks Geoff, that might be it. Unfortunately I'm working with an enormous dataset (there are 27k tags, i'm not sure how that happened), so this request times out
Ok, i just tried this on our more manageable staging dataset. From what I recall, the
match
operator requires matching all the tags, I think I want to use
in
. This works:

*[
  _type == "article"
  && tags[]->slug match ["tag-a"]
]{
  title,
  tags[]->,
}[0..5]
This returns 0 results:
*[
  _type == "article"
  && tags[]->slug match ["tag-a", "tag-b"]
]{
  title,
  tags[]->,
}[0..5]
This gives me an error:
*[
  _type == "article"
  && tags[]->slug in ["tag-a", "tag-b"]
]{
  tags[]->,
}[0..5]
Query error
No function in() defined for arguments (array, array)

Sorry Joseph. My fault for misunderstanding. If you want to match
tag-a
or
tag-b
, can you see if this works?

*[_type == "article" && tags[]->slug.current match ["tag-a"] || tags[]->slug.current match ["tag-b"]]
I’ve given that a try on one of my schemas that I think resemble yours, but if I’ve missed the mark once more, my apologies.
No worries, I wasn't clear on my search being an "Or" situation. this does the trick, thank you!
Oh good! Thanks Joseph!

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?