Help needed with expanding and filtering content objects in a GROQ query.

3 replies
Last updated: Dec 30, 2020
Hello,
Reposting this issue I'm still stuck on. Would love some help!
I've been chipping away at it and have been able to expand out my properties reference like so:


*[_type == "route" && slug.current == $slug][0]{

page-> {

content[] {

...,

},

'properties': content[_type == 'properties']->{...,}

}

The above query will give me a
properties
array with my document objects but the objects come in without a
_key
Furthermore, my
content
array still includes the unexpanded
properties
objects, in effect rendering the
property
objects twice (but only rendering them correctly from the expanded
properties
array).
I have also tried writing my query like so:


*[_type == "route" && slug.current == $slug][0]{

page->{content[]{...,"properties": @->}}

}

This will return the expanded
properties
reference within the
content
array but those expanded references are now nested within a sub object labeled 'properties' which gets ignored by
RenderSections

I would really appreciate any help with this - I'm stumped!

Here is my original issue:

Hello,

Reposting this issue I'm still having trouble with.  I'm building off the Next.JS Landing starter.
I have a reference in my 
page
 schema to a document schema named 
properties
When I

Here is my GROQ query:
`*const* pageQuery = groq``

*[_type == "route" && slug.current == $slug][0]{
 
page-> {
  
...,
  
content[] {
   
...,
   
cta {
    
...,
    
route->
   
},
   
ctas[] {
    
...,
    
route->
   
}
  
}
 
}
`}`;`

Here is the query response as it originally returned, with 
content
 unexpanded.  You can see that the referential page sections (object indexes 1, 2 & 3) are being returned as reference objects but missing their section data:

{

"content": [

{

"_key": "c6a0e210ec09",

"_type": "textSection",

"heading": "Locations",

"label": "text",

"text": [

{

"_key": "873b292f564d",

"_type": "block",

"children": [

{

"_key": "c91a82e9a3f7",

"_type": "span",

"marks": [],

"text": "this is the locations page"

}

],

"markDefs": [],

"style": "normal"

}

]

},

{

"_key": "6ec96907b41a",

"_ref": "2f65763c-05f7-4c96-bde8-347104c1e9d5",

"_type": "properties"

},

{

"_key": "c90b4bccb9db",

"_ref": "aa078f7f-4a20-4d53-88af-50d46841854b",

"_type": "properties"

},

{

"_key": "bbbf684bda6c",

"_ref": "beeca3b0-4d48-40b0-9156-c12c970f2fca",

"_type": "properties"

},

{

"_key": "3b7fb58ab9b8",

"_type": "vimeo",

"url": "<https://player.vimeo.com/video/490822978>"

},

{

"_key": "b3dcc4e0c737",

"_type": "imageSection",

"image": {

"_type": "figure",

"asset": {

"_ref": "image-4d9705b8f7dade773a622d7d4b8571c7ce52ca6e-480x360-jpg",

"_type": "reference"

}

}

}

]

}

And here is the response with `content`expanded (the query:
`*const* pageQuery = groq``

*[_type == "route" && slug.current == $slug][0]{
 
page-> {
  
...,
  
content[]-> {
   
...,
   
cta {
    
...,
    
route->
   
},
   
ctas[] {
    
...,
    
route->
   
}
  
}
 
}
`}`;` ).
You can see that the referential page sections are now fully returned but the non referential page sections get returned as empty objects:


{

"content": [

{},

{

"_createdAt": "2020-12-12T20:38:57Z",

"_id": "2f65763c-05f7-4c96-bde8-347104c1e9d5",

"_rev": "f0VLqLQQvsZkIeAZEumCL9",

"_type": "properties",

"_updatedAt": "2020-12-15T16:42:15Z",

"city": "Los Angeles",

"clientName": "Kevin DanChisko",

"email": "<mailto:kevin@vrlocations.com|kevin@vrlocations.com>",

"image": [

{

"_key": "b199d238a3c9",

"_type": "image",

"asset": {

"_ref": "image-e1890f4efa3933f01a63e264faa4aebf69dec959-724x516-png",

"_type": "reference"

}

}

],

"phone": 8056785432,

"state": "CA",

"streetAddress": "34 Maltman Ave",

"title": "Midcentury House",

"zip": "90042"

},

{

"_createdAt": "2020-12-12T20:02:11Z",

"_id": "aa078f7f-4a20-4d53-88af-50d46841854b",

"_rev": "XszOhSOqE7Zees1yLPzc2G",

"_type": "properties",

"_updatedAt": "2020-12-12T20:02:11Z",

"city": "Agua Dulce",

"clientName": "Bumpkin Joe",

"email": "<mailto:joe@vrlocations.com|joe@vrlocations.com>",

"image": [

{

"_key": "fe6a3dfca81c",

"_type": "image",

"asset": {

"_ref": "image-8e1fbc5de369a545711edbe620a87df0111ad172-722x512-png",

"_type": "reference"

}

}

],

"phone": 8184348243,

"state": "CA",

"streetAddress": "1234 Chew Lane",

"title": "Cabin",

"zip": "90343"

},

{

"_createdAt": "2020-12-14T19:20:33Z",

"_id": "beeca3b0-4d48-40b0-9156-c12c970f2fca",

"_rev": "YCAL9E3SVwkigJCUFiaELz",

"_type": "properties",

"_updatedAt": "2020-12-15T17:23:07Z",

"city": "Los Angeles",

"clientName": "Bubba Sparxx",

"image": [

{

"_key": "37595fda819c",

"_type": "image",

"asset": {

"_ref": "image-11bbfc6064312cbc884fe68252e54bb08609888e-2230x1392-png",

"_type": "reference"

}

}

],

"state": "CA",

"streetAddress": "123 Fake St",

"title": "Hill Street Underground",

"vimeo": {

"_type": "vimeo",

"url": "<https://player.vimeo.com/video/490822978>"

},

"zip": "90042"

},

{},

{}

]

}

I would love it if someone could help me understand what I might be doing wrong.  I'm as interested in the 
why as I am in the how to expand and return all my 
content
 objects.  Thank you!
AI Update

I can see the issue you're running into! This is a common challenge when working with arrays that contain both inline objects and references. Let me break down what's happening and how to fix it.

The Problem

When you use content[]->, GROQ tries to dereference every item in the array. Items that are references (like your properties) get properly dereferenced, but items that are already inline objects (like textSection, vimeo, imageSection) return empty objects because there's nothing to dereference.

The Solution

You need to handle mixed content types in your array. The best approach is to use conditional dereferencing with the @ operator (which refers to the current item):

*[_type == "route" && slug.current == $slug][0]{
  page-> {
    ...,
    content[] {
      ...,
      _type == "properties" => @->,
      cta {
        ...,
        route->
      },
      ctas[] {
        ...,
        route->
      }
    }
  }
}

This uses GROQ's conditional syntax: when _type == "properties" is true, it dereferences with @->, otherwise it just spreads the existing object with ....

Alternative: Using select()

Another approach is using the select() function, which can be cleaner for this use case:

*[_type == "route" && slug.current == $slug][0]{
  page-> {
    ...,
    content[] {
      ...,
      select(
        _type == "properties" => @->,
        @ // Return the item as-is for non-references
      ),
      cta {
        ...,
        route->
      },
      ctas[] {
        ...,
        route->
      }
    }
  }
}

Why Your _key Was Missing

When you used the separate 'properties': content[_type == 'properties']-> approach, you lost the _key because you were creating a new array projection outside the original content array context. The _key exists on the reference object in the content array, not on the referenced document itself.

The Why

The reference access operator (->) in GROQ is designed to dereference references by internally executing a subquery to fetch the referenced document. When you write content[]->, you're telling GROQ "dereference everything in this array," which doesn't work for mixed content. By using conditional logic, you're saying "only dereference items that are actually references," which preserves your inline objects while expanding your properties references.

This should give you a single content array with all items properly expanded, maintaining their _key values, and working correctly with your RenderSections component!

Hi Robert! Thanks for sharing your deep-dive into dereferencing and arrays of multiple types.
Looks like you've already covered quite some ground, including how to filter out the embedded
properties
objects using
content[_type == 'properties']
.
Instead of filtering, unless you don't mind adapting how your Next.js starter works with
RenderSections
(which would be an alternative way to resolve this), you could opt for a conditional instead. You can find some examples in this section: https://www.sanity.io/docs/query-cheat-sheet#conditionals-64a36d80be73
For your specific query, something like this should work, leaving the
_key
intact and avoiding the duplication you ran into earlier:
*[_type == "route" && slug.current == $slug][0]{
  page-> {
    content[] {
      ...,
      _type == "properties" => @->
    }
  }
Hope this helps!
🙂
user M
thank you so much. I know there was a lot to my question I appreciate you taking the time to help me.This works perfectly!
Could you help me understand what the fat arrow is doing inside the projection you suggested? As I understand from the GROQ cheatsheet `=&gt;`sets up a conditional but isn't such a conditional redundant after we specify `_type == "properties"`in the query?
Obviously this is not the case because your suggestion works perfectly but knowing why would really help me better understand GROQ.
Thanks again!!
Hi
user M
I have another quick (I hope) follow up question:In the example you provided above, where would I implement filtering?
For example if I change the query to:

*[_type == "route"]{

page-> {

content[] {

...,

_type == "properties" &&  category match 'homes' => @->

},}}
I'm neither filtering the results to only return properties whose
category
== 'homes' nor expanding the properties reference any longer.Thanks again!

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?