GROQ reference operator (->) not working in nested arrays

11 replies
Last updated: Jan 28, 2026
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!

Show original thread
11 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?