How to expand image asset references in PortableText/BlockContent with GROQ?

7 replies
Last updated: Apr 19, 2023
So, I'm trying to render a PortableText with images in a React application (using @portabletext/react and Sanity v3). I have followed the ReadMe on how to create a custom react component to display images, but the BlockContent returned from my query contains objects of images with a reference to the
image->asset->_ref
instead of the expanded image url. How can I get the expanded image references directly from my GROQ query?
In other queries I can do
*[_type == "post"] { "image": image.asset->url }
but I'm not sure how to do this for BlockContent.
This is the blog template without any big modifications.
AI Update

When querying PortableText with image references, you need to use GROQ's conditional operator and the @ operator to expand inline references. Here's how to solve this:

The Solution

Use this GROQ query pattern to expand image references within your PortableText blocks:

*[_type == "post"] {
  title,
  body[]{
    ...,
    _type == "image" => {
      ...,
      "asset": @.asset->
    }
  }
}

How It Works

The key is using the @ operator, which refers to the root value of the current scope. When you iterate through the PortableText array with body[], the @ operator lets you access the current block object and dereference its asset.

Breaking it down:

  • body[] - Iterates through all blocks in your PortableText array
  • ... - Spreads all existing properties of each block
  • _type == "image" => {...} - Conditionally handles blocks that are images
  • @.asset-> - Dereferences the asset for the current image block

For Custom Reference Types

If you have custom reference types in your PortableText (like in the children array), use the same pattern:

children[]{
  ...,
  _type == 'orderReceiptPlaceholderRef' => {
    "reference": @->
  }
}

This approach works because:

  1. Some children are regular spans, others are references
  2. children[]-> would dereference everything (leaving non-references as null)
  3. children[] alone includes everything but doesn't expand references
  4. Using the conditional with @-> only dereferences when needed

You can find more examples of GROQ conditionals in the GROQ query cheat sheet, and this solution is based on a community discussion about this exact issue.

Show original thread
7 replies
My query
The data returned
Of course I find the answer immediately after asking the question πŸ˜„ https://www.sanity.io/help/block-content-image-materializing
But then I have a follow-up question: The query suggested in the article includes the
asset
property for all blocks with
null
as value if the source block does not contain the
asset
property. Is there a way to only include the "asset"-property in the result for blocks that definitely contains an
asset
property?
Never mind, figured out that as well... Oh, the wonders of rubber ducking oneself.... 🀣
This is delightful to read πŸ™‚ If you want some πŸ’… for your portable text, you should look at these:β€’
inline blocks β€’
custom annotations and more
user J
Thanks for those links! πŸ˜„ I already made a youtube embed following one of the guides on the website. Pretty cool! Didn't know you could have those kind of inline blocks as well
Nice ones, I had really hoped for an complete example of how to create an annotation component for color because I can not get it to work render the selected color πŸ˜•
the custom color picker stores the color in correct way, like

      "_type": "block",
      "children": [
        {
          "_key": "6dce5ea4c2d4",
          "_type": "span",
          "marks": [
            "021bc167e2d2"
          ],
          "text": "I want to set color on this text!"
        }
      ],
      "markDefs": [
        {
          "_key": "021bc167e2d2",
          "_type": "color",
          "color": "rgb(0, 113, 119)"
        }
      ],
My custom TextColor component set to components: { annotation: TextColor } in Block schema

const InlineAnnotation = styled.span`
  border: none;
  ${(props) => props.textColor};
`

const TextColor = (props) => {
  const { value, renderDefault } = props
  const { color } = value
  console.log(value, color)

  return (
    <InlineAnnotation
      className="MyCustomAnnotatianWhereColorIsSet"
      textColor={{ color }}
    >
      {renderDefault({ ...props })}
    </InlineAnnotation>
  )
}
console.log, where we see that it actually pickup selected color.

{_key: '021bc167e2d2', _type: 'color', color: 'rgb(0, 113, 119)'} 'rgb(0, 113, 119)'
{_key: 'a0f14ee6a1c6', _type: 'color', color: 'rgb(249, 93, 122)'} 'rgb(249, 93, 122)'
renders in portableText UI

<span class="sc-dOrDzm hSVqgX MyCustomAnnotatianWhereColorIsSet">
  <span class="sc-eyLAWx erPojB">
    <span data-annotation="">
      <span data-slate-string="true">
        here is the next text for color!
      </span>
    </span>
  </span>
</span>
But the .erPoJB class comes from built in &lt;Annotation&gt; with below css and I have no idea how to solve this. , to not render dashed bottom border nor setting the color
πŸ˜•
{
  -webkit-text-decoration: none;
  text-decoration: none;
  display: inline;
  background-color: #fff;
  border-bottom: 1px dashed #565d67;
  color: #565d67;
}
for reference, β€œmy” .hSVqgX got below css:

border: none;
color: rgb(249,93,122);
The portableBlock component in the studio looks like first screen shot

Could you please give me a hint how to render the texts colors in the β€œRTE”?
user J
πŸ™
(In V2 we just used the blockedEditor in the block content schema, very simple and just worked
πŸ™‚
blockEditor: {
  render: ({ color, children }) => {
    return (
      <span style={{ color }}>{children}</span>
    )
  },
},
)

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?