Issue with nested references in GROQ query in Slack thread

11 replies
Last updated: Oct 23, 2020
OK, so I have a document that contains an array objects, and each object have a reference to another document. GROQ, exemplified with books:

*[_type == "collection"] {

"title": collection_title,

"books": collection_books[]{

"title": collection_book->book_title,

"persons": collection_book->persons[]{

"type": type,

"firstname": person->firstname

}

}

}

Problem here is that
person->firstname
doesn't return anything. If I just write
person
then _ref and _type is returned as expected, but -> doesn't seem to work. Are there some limitations on -> when nested like this?
AI Update

The -> operator should work in nested array projections like this—there's no inherent limitation that prevents person->firstname from working when nested. The documentation shows examples like castMembers[].person->name working just fine. So let's troubleshoot the actual cause of your issue.

Most Likely Causes

1. Field Name Mismatch

Double-check that the field is actually named firstname in your person document schema. Try querying just the dereferenced person to see what fields exist:

*[_type == "collection"][0].collection_books[0]->persons[0].person->

This will show you the complete person document structure. The field might be named firstName, first_name, name.first, or something else entirely.

2. Draft vs Published Documents (Very Common Gotcha)

According to the Sanity perspectives documentation, when you have unpublished changes, Sanity creates a draft document with an ID prefixed by drafts.. If your referenced person document exists only as a draft, or if you're querying published documents that reference draft versions, the dereference might fail or return incomplete data.

Try specifying the previewDrafts perspective in your query parameters, which prioritizes drafts over published versions:

// In your client configuration
const client = createClient({
  // ... other config
  perspective: 'previewDrafts',
  useCdn: false // Important: CDN doesn't support draft content
})

Or test in Vision with the perspective dropdown set to "Preview Drafts".

3. Incorrect Field Path

Looking at your query structure:

"persons": collection_book->persons[]{
  "type": type,
  "firstname": person->firstname
}

Make sure persons is actually an array of objects that each contain a person reference field. The structure should be something like:

{
  persons: [
    {
      type: "author",
      person: { _ref: "person-id-123", _type: "reference" }
    }
  ]
}

Verify this by checking what person alone returns (without the ->):

*[_type == "collection"][0].collection_books[0]->persons[0].person

You should see {"_ref": "...", "_type": "reference"}. If you see null or undefined, then the field path is wrong.

Debugging Steps

Test incrementally from the inside out:

// Step 1: Can you get the book?
*[_type == "collection"][0].collection_books[0]->

// Step 2: Can you get the persons array?
*[_type == "collection"][0].collection_books[0]->persons

// Step 3: Can you get a specific person reference?
*[_type == "collection"][0].collection_books[0]->persons[0].person

// Step 4: Can you dereference it?
*[_type == "collection"][0].collection_books[0]->persons[0].person->

// Step 5: Can you get the specific field?
*[_type == "collection"][0].collection_books[0]->persons[0].person->firstname

One of these steps will reveal where the query breaks down.

The Query Should Work As-Is

Your nested dereference pattern is valid GROQ syntax. According to the reference access operator documentation, the -> operator internally executes a subquery to fetch the referenced document, and this works at any nesting level. The issue is almost certainly one of:

  • Wrong field name
  • Draft/published document mismatch
  • Incorrect field path in your schema
  • The reference field doesn't actually contain a valid reference

Work through those debugging steps and you should find the culprit!

Try
person->.firstname
and if they’re already named
type
and
firstname
you could try

{
  type,
  person->.firstname
}
Sorry, User, but doesn't help. But, thanks! type is returned correctly, as that is a value in the object, but person-> is a reference, and doesn't return anything.
If I use
person->.firstname
instead of
"firstname": person->.firstname
I get an error: "Must be an attribute or a string key".
try
"firstname": @->.firstname
@
denotes the current item in the array
Actually, it depends on your data structure, so an example document would help
I solved this particular case by rewriting the structure of the query to a more suitable format. Anyways, I ran into another case of the same:

 "files": collection_set->set_downloads[]{
        "comments": download_comments,
        "type": download_type,
        "url": download_file.asset->url
    },
comments and type works fine, but url returns nothing. I "hacked" it by doing some string replacements on
download_file.asset["_ref"]
instead, but should be fixed the correct way. Tried with @, but no luck. Some relevant schemas attached.
user J
☝️
That looks correct to me. Is it a public dataset? If so you could send me an http url with query and I could tinker with it to see if there's something we're missing
Sure! See PM. Thanks!
This seems to work, you get the extra
set_downloads
level, tho. But at least you get the url
*[_type == 'collection']{
  "files": collection_sets[]{
    ...@.collection_set->{
      set_downloads[]{
        "comments": download_comments,
        "type": download_type,
        "url": download_file.asset->url
      }
    }
  }
}
Thanks, this works!

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?