GROQ query: Filter by nested array object type and array contains value

8 replies
Last updated: Aug 28, 2020
For a document with schema (I removed unrelated parts):
{
  type: "document",
  name: "client",
  fields: [
    /*...*/,
    {
      type: "array",
      name: "provider",
      of: [
        {
          type: "object",
          name: "upis",
          fields: [
            /*...*/,
            {
              type: "number",
              name: "carrierId",
              title: "Carrier ID",
            },
          ],
        },
      ],
    },
    {
      type: "array",
      name: "activation",
      of: [
        {
          type: "object",
          name: "upis",
          title: "Ūpis (Cashreg)",
          fields: [
            /*...*/
          ],
        },
        {
          type: "object",
          name: "busmaticScheduler",
          title: "Busmatic Scheduler",
          fields: [
            {
              type: "array",
              name: "plugins",
              title: "Plugins",
              of: [{ type: "string" }],
              options: {
                list: [
                  {
                    title: "Ūpis for City",
                    value: "upis-city",
                  },
                  {
                    title: "Import API",
                    value: "import-api",
                  },
                ],
              },
            },
          ],
        },
      ],
    },
  ],
},
I want to fetch based on
provider[].carrierId
&&
activation[]._type == "busmaticScheduler" && "upis-city" in activation[].plugins
, but that results in an empty result, when queried as such:
*[
  _type == "client"
  && provider[].carrierId == 5
  && activation[]._type == "busmaticScheduler"
  && "upis-city" in activation[].plugins
]
Or:

*[
  _type == "client"
  && provider[].carrierId == 5
  && activation[]._type == "busmaticScheduler"
  && activation["upis-city" in plugins]
]
Maybe I am trying to accomplish the impossible? Or have I got something wrong?
AI Update

The issue you're encountering is a common GROQ pitfall when working with nested arrays. When you use array[] syntax multiple times in a filter, the operators don't associate the way you might expect - they expand to flat arrays and compare arrays to values, which doesn't work.

Your query:

*[
  _type == "client"
  && provider[].carrierId == 5
  && activation[]._type == "busmaticScheduler"
  && "upis-city" in activation[].plugins
]

The problem is that provider[].carrierId == 5 becomes [1, 2, 5, ...] == 5 (comparing an array to a value), and "upis-city" in activation[].plugins tries to check if the string exists in a flattened array of all plugins from all activation items.

The solution is to use count() with filtered arrays:

*[
  _type == "client"
  && count(provider[carrierId == 5]) > 0
  && count(activation[_type == "busmaticScheduler" && "upis-city" in plugins]) > 0
]

This works because:

  1. provider[carrierId == 5] filters the provider array to only items where carrierId equals 5
  2. count(...) checks if any items match (count > 0)
  3. For the nested condition, you can combine both checks in a single filter: activation[_type == "busmaticScheduler" && "upis-city" in plugins]

As explained in this community answer about querying nested arrays, the key insight is that array filters with conditions inside brackets return only matching elements, while the projection syntax with [] flattens everything.

If you also want to clean up your projection to only return the matching items, you can do:

*[
  _type == "client"
  && count(provider[carrierId == 5]) > 0
  && count(activation[_type == "busmaticScheduler" && "upis-city" in plugins]) > 0
]{
  ...,
  "provider": provider[carrierId == 5],
  "activation": activation[_type == "busmaticScheduler" && "upis-city" in plugins]
}

This way your results only show the provider and activation items that actually matched your filter criteria, making it easier to see why each document was returned - similar to the pattern shown in the nested arrays example where they filtered down to only relevant pricing plans within variants.

Show original thread
8 replies
These will both return arrays, so comparing them to a string doesn't make sense?
provider[].carrierId
activation[]._type
Try

5 in activation[]._type &&
"busmaticScheduler" in activation[]._type
?
Well, but the query works except for the
"upis-city" in activation[].plugins
And not only it works, but also returns the expected results. (Or maybe I am hitting an unexpected behavior and my data appears to be OK with it for now)
But you're right on that, I can rework them all to `in`'s. The real problem though is with the
in activation[].plugins
part.
*[
  _type == "client"
  && "upis" in provider[]._type
  && 5 in provider[].carrierId
  && "busmaticScheduler" in activation[]._type
  && defined(activation[_type == "busmaticScheduler" && "upis-city" in plugins])
] {
  name,
  sanity
}
This seems to have done it.
And actually, I can reduce it to:
*[
  _type == "client"
  && "upis" in provider[]._type
  && $carrierId in provider[].carrierId
  && defined(activation[_type == "busmaticScheduler" && "upis-city" in plugins])
] {
  name,
  sanity
}
Had to use the
defined
function on
plugins
part, otherwise the query didn't return anything.

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?