Querying references inside a Portable text field in Sanity

8 replies
Last updated: Jan 11, 2022
Following the field level translation documentation, I have a content object that contains the languages as keys. Normally I can access any field like this.

"content": coalesce(content[$lang], content.en)

This content is a Portable text field, and I want to query the current language or fallback, but I want to be able to reach the references inside that object, like referenced images.

Say I want to access this asset of
pageImage
that is a block inside `content`:

_type == "pageImage" => {

...,

"alt": coalesce(alt[$lang], alt.en),

"imageBlock": imageBlock {

"asset": asset->url,

"dimensions": asset->metadata.dimensions

},

},

This doesn’t work:


"content": coalesce(content[$lang], content.en) {

....,

_type == "pageImage" => {

...,

alt,

"imageBlock": imageBlock {

"asset": asset->url,

"dimensions": asset->metadata.dimensions

},

},

}

How would you query that?
AI Update

I understand you're trying to project into a Portable Text field that uses field-level translation, where you need to access references (like image assets) within the translated content blocks.

The key is to use the array projection operator [] immediately after your coalesce expression. This tells GROQ to iterate over each element in the array and apply the projection to each block:

"content": coalesce(content[$lang], content.en)[] {
  ...,
  _type == "pageImage" => {
    ...,
    "alt": coalesce(alt[$lang], alt.en),
    "imageBlock": imageBlock {
      "asset": asset->url,
      "dimensions": asset->metadata.dimensions
    }
  }
}

The [] operator is what enables you to then use conditional projections (the => operator) and dereference operations (->) within each block.

Here's a more complete example that handles different block types:

"content": coalesce(content[$lang], content.en)[] {
  _type != "pageImage" => @,
  _type == "pageImage" => {
    _type,
    _key,
    "alt": coalesce(alt[$lang], alt.en),
    "imageBlock": imageBlock {
      "asset": asset->url,
      "dimensions": asset->metadata.dimensions
    }
  }
}

In this pattern:

  • The @ operator (identity operator from GROQ's pipeline components) passes through non-pageImage blocks unchanged
  • pageImage blocks get the special projection with language fallback and reference dereferencing
  • The asset->url syntax works because you're inside the projection context where the reference operator can resolve

The array projection operator [] is part of GROQ's pipeline architecture, where operations are chained together to transform data. The coalesce() function returns the first non-null value, and when followed by [], that value (which should be an array) is projected element-by-element.

Important note: While this approach works, the Sanity documentation on localization recommends against using field-level translation for Portable Text fields, as it can quickly consume attribute limits and create complex data structures. Document-level localization is generally preferred for Portable Text content.

Hi! Using
coalesce()
in this way should work fine. Which API version are you on?
The api version is “2021-03-25”
If I only query this

"content": coalesce(content[$lang], content.en) {

...,

}

I get null. Should I change the api version to something else?
Odd. That API version should work.
Can you do just:
"content": coalesce(content[$lang], content.en)
and see?

Also, if that returns null, how about just:

"content": coalesce(content[$lang])
"content": coalesce(content[$lang], content.en)
works well. the problem is that I cant access the reference of the images inside that object if I do only that.
In the output of content I will get
"imageBlock": {
        "_type": "image",
        "asset": {
          "_ref": "image-e54e00da4b0c81d18aea3260fd7a551b5f914121-702x490-gif",
          "_type": "reference"
        }
How do I access that reference?
I found the solution! After coalesce() I should query inside the array, not an object key.

"content": coalesce(content[$lang], content.en)[] {

...,

_type == "image" => {

...,

alt,

"imageBlock": imageBlock {

"asset": asset->url,

"dimensions": asset->metadata.dimensions

},

}

}
Ah, for some reason I thought
content[...]
was an object. 🙂 Great!

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?