GROQ query for getting references from arrays in another array in Sanity.io

12 replies
Last updated: Apr 9, 2020
I have a GROQ question about getting references from an arrays that are included in another array, if that is possible.

Field and document names has been altered for simplicityI currently have a document called Tag, its only field is a string.
My second doucument (
article) has an array of reference to Tag (tags) - these are tags for this documentMy third document (
articleList) is meant to list articles with certain tags. So this document also has an array of references to Tag (articleTags)
I've tried different versions of this query but they all give me either zero results or an error:
This for instance gives:
No function in() defined for arguments (array, array)

// just query the first (and only) article list
*[_type == 'articleList'][0] {
  "articlesToShow": *[_type == 'article' && tags[]._ref in ^.articleTags[]._ref]
}
If instead only let an
article have one tag (tag) instead of multiple, the following query works. But in real life, most articles would need more than one tag:
// just query the first (and only) article list
*[_type == 'articleList'][0] {
  "articlesToShow": *[_type == 'article' && tag._ref in ^.articleTags[]._ref]
}
Any thoughts?
AI Update

You're running into the classic GROQ challenge of checking if any element in one array matches any element in another array! The error No function in() defined for arguments (array, array) happens because the in operator can't directly compare two arrays.

The solution is to use the count() + @ pattern to check for array overlap. Here's how to fix your query:

*[_type == 'articleList'][0] {
  "articlesToShow": *[
    _type == 'article' 
    && count(tags[]._ref[@ in ^.articleTags[]._ref]) > 0
  ]
}

How this works:

  1. tags[]._ref projects your article's tag references into a simple array like ["tag1", "tag2"]
  2. The [@ in ^.articleTags[]._ref] filters this array to only elements that exist in the parent's articleTags
  3. count(...) counts how many matches there are
  4. > 0 means "at least one tag matches"

The ^ operator accesses the parent scope (your articleList document) from within the subquery.

Alternative approach if you want it more explicit:

*[_type == 'articleList'][0] {
  articleTags,
  "articlesToShow": *[
    _type == 'article' 
    && count(tags[@._ref in ^.^.articleTags[]._ref]) > 0
  ]
}

Note the double ^.^. - this is because tags[query] creates its own closure, so you need to go up two levels to reach the articleList document.

This pattern is one of the trickier parts of GROQ, but it's super powerful once you get the hang of it! The community thread I found shows several examples of this technique in action, including a detailed breakdown for matching related posts by tags.

If you want to have tag x OR tag y, you need to write mulitple `in`s. Here’s a simplified example:
const tags = ['tag1', 'tag2']
const query = `*[_type == 'article' && (${tags.map(t => `'${t}' in tags`).join(' || ')})]`
Hope that helps!
Thank you. That didn't help though, feel in this case that I might need something more than a simplified example.Didn't even know that what looks like a js map and arrow function was possible in GROQ
🙂
Tried above code with some modifications but received error on

Param $ referenced, but not provided
Removing the first $ gives me:

String literal expected
Hi (Removed Name), could you share your code for the query? Looks like it might be an issue with backticks, brackets or something similar (or not having imported React).
As an alternative, you could also try this to go around the issue:

...
'articlesToShow': *[_type == 'article' && references(^.tags[]._ref)]
...
Here is the same but the tag string extracted to a variable, with how the query will end up looking like:
const tags = ['tag1', 'tag2']
const tagString = tags.map(t => `'${t}' in tags`).join(' || ')
// Output: 'tag1' in tags || 'tag2' in tags
const query = `*[_type == 'article' && (${tagString})]`
// Output: `*[_type == 'article' && ('tag1' in tags || 'tag2' in tags)]`
But what (Removed Name) said might be more what you’re looking for
👆
I was so into just writing GROQ that I didn't realise your suggestion (Removed Name) seems to be to compose the query using js and then finally just executing that last line, am I correct in my assumption?
My idea is simply to make one request if that is possible:
Query for "the current document" and join documents by making some sort of subquery.
So here are the three schemas in play, and an explanation at the end
Tag document

export default {
  name: 'tag',
  type: 'document',
  title: 'Tag',
  fields: [
    {
      name: 'title',
      type: 'string',
      title: 'Title'
    }
  ]
}
article doument

export default {
  name: 'article',
  type: 'document',
  title: 'Article',
  fields: [
    {
      name: 'title',
      type: 'string',
      title: 'Title'
    },
    {
      name: 'tags',
      type: 'array',
      title: 'Tags',
      type: 'array',
      of: [
        {
          type: 'reference',
          to: [{ type: 'tag' }]
        }
      ]
    }
  ]
}
article list document

export default {
  name: 'articleList',
  type: 'document',
  title: 'List of articles',
  fields: [
    {
      name: 'title',
      type: 'string',
      title: 'Title'
    },
    {
      name: 'articlesToShow',
      type: 'array',
      title: 'Show articles with the following tags',
      of: [
        {
          type: 'reference',
          to: [{ type: 'tag' }],
        },
      ],
    }
  ]
}

When querying for the article list, I would also like to query for the articles that are tagged with any of the tags that the current article list is tagged with.

Might be a better way to do this though..
So basically. Given the schemas above, the following GROQ would yield all articles tagged with the first articlesToShow tag:

*[_type == 'articleList'][]{
  'taggedArticles': *[_type == 'article' && tags[0]._ref in ^.articlesToShow[]._ref]
}
This would then work, giving me all articles tagged with the tags that the articleList has, but it feels kinda strange having to specify indices like this. It also creates a limit on how many tags we can support:


*[_type == 'articleList'][]{
  'taggedArticles': *[
     _type == 'article'
     && tags[0]._ref in ^.articlesToShow[]._ref
  	 || tags[1]._ref in ^.articlesToShow[]._ref
  	 || tags[2]._ref in ^.articlesToShow[]._ref
	]
}

Indeed, this approach should give you what you need but as you said, it can get a bit lengthy unless you query multiple times and implement what (Removed Name) suggested earlier. Have you tried the alternative query I posted above? 🙂
...
'articlesToShow': *[_type == 'article' && references(^.tags[]._ref)]
...
Yes I tried you solution but it didn't yield any results. Assuming it is because the tags-field references other tag-documents rather than articles
Looks like I missed
articlesToShow
— I assumed both arrays were called
tags
, sorry 🙂 Try this:
*[_type == 'articleList'] {
  'articlesToShow': *[_type == 'article' && references(^.articlesToShow[]._ref)]{
    title
  }
}
Thanks (Removed Name), that worked fine.A different take on the issue.
I must have seriously misunderstood how
references()
worked.. It seems to check (for each article I guess) if it references any of the articlesToShow refs?
That’s right, it checks for each article if it has
_ref
values from the
articlesToShow
array in its own references. These
_ref
values are essentially tag `_id`s, so a reference to the same tag will give the same
_ref
in
tags
,
articlesToShow
or any other field name you give to the array of references, as long as they all refer to the
tag
document type.

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?