GROQ v1 auto-dereferences arrays, v2+ returns refs - how to dereference?

7 replies
Last updated: Jan 27, 2026
in groq v1 the following query:

*[_id == '90ce29b2-cab4-4790-bd97-57e1a97adaf5'][0] {
  'blocks': blocks[]->,
  ...}
returns all the blocks that it can dereference as dereferenced objects, and
everything else as null:


// v1
result: {
  //...
  "blocks": [
  null, // <----------------- missing ref
  {
    "_createdAt": "2022-06-15T17:25:28Z",
    "_id": "f66bfdca-f03a-4bfe-a4fa-cda7537a448a",
    "_rev": "kwp71lZJIGHA037Eneiyj2",
    "_type": "prose",
    "_updatedAt": "2022-06-29T16:37:37Z",
    "content": [ /*...*/]
  }
]
  //...
}
Newer versions don't seem to do anything to the array in question returning
them as references instead.


// v2021-03-25
result: {
  //...
  "blocks": [
    {
      "_key": "293cc22154b1",
      "_ref": "968147d3-a988-4b05-8faa-21a1d3182650",
      "_type": "linkMenuRef",
      "_weak": true
    },
    {
      "_key": "f280c4e697e1",
      "_ref": "f66bfdca-f03a-4bfe-a4fa-cda7537a448a",
      "_type": "proseRef",
      "_weak": true
    }
  ],
  //...
}
Is there still a way to do this with relatively simple syntax?
AI Update

I understand you're experiencing a change in how blocks[]-> behaves between GROQ v1 and newer API versions like v2021-03-25. This is indeed a documented change related to API versioning.

The issue you're seeing is real: in v1, blocks[]-> would dereference references and return null for missing ones, but in v2021-03-25 and later, the dereferencing behavior changed and the syntax returns the reference objects themselves when placed after other projections.

Based on a community discussion around this exact issue, there's a documented workaround:

The solution is to place the spread operator (...) BEFORE your blocks[]-> projection:

*[_id == '90ce29b2-cab4-4790-bd97-57e1a97adaf5'][0] {
  ...,
  'blocks': blocks[]->
}

This ordering matters in v2021-03-25 and later versions. When you put the spread operator first, the dereferencing works as expected, returning dereferenced objects with null for missing references.

This does NOT work:

*[_id == '90ce29b2-cab4-4790-bd97-57e1a97adaf5'][0] {
  'blocks': blocks[]->,
  ...
}

The order of operations in the projection affects how the dereferencing is processed in newer API versions. This appears to be a parsing quirk in how GROQ handles projections when combining dereferencing with the spread operator.

Alternative approach if you want to filter out nulls entirely:

If you want to exclude missing references rather than having null values in your array, you can filter them after dereferencing:

*[_id == '90ce29b2-cab4-4790-bd97-57e1a97adaf5'][0] {
  ...,
  'blocks': array::compact(blocks[]->)
}

Or use a filter to only include defined references:

*[_id == '90ce29b2-cab4-4790-bd97-57e1a97adaf5'][0] {
  ...,
  'blocks': blocks[defined(@->)]->
}

This is a known behavior difference between v1 and v2021-03-25+ related to how the GROQ parser handles projection ordering when dereferencing is involved. The workaround of placing ... first in your projection should restore the v1-like behavior you're looking for.

Show original thread
7 replies

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?