How to query images from portable text body in Sanity slug page?

25 replies
Last updated: May 27, 2022
I'm trying to get the images from inside the body of a [slug].js in my blog, but I'm not getting it right ... can someone help me with some clues?So far I'm stuck at
ReferenceError: image is not defined. I've tried a few different ways to get a hold of the image.. but no luck so far..
AI Update

It sounds like you're trying to query images that are embedded inside a Portable Text body field in your blog posts, but you're getting a "ReferenceError: image is not defined" error. This is a common issue when working with Sanity's Portable Text and images.

The problem is likely that when you query your blog post, the image references inside the body field aren't being "expanded" - they're just coming back as reference objects rather than the full image data you need.

The Solution: Use GROQ to Expand Image References

When you have images inside a Portable Text field (like your body array), you need to use GROQ's projection syntax to "materialize" or expand those image asset references. Here's how to do it:

*[_type == "post" && slug.current == $slug][0] {
  title,
  body[]{
    ...,
    _type == "image" => {
      ...,
      asset->
    }
  }
}

Let me break this down:

  • body[] - iterate through each block in the body array
  • ... - spread all the existing properties
  • _type == "image" => { ... } - when you encounter an image block, expand it
  • asset-> - follow the asset reference to get the full image document

For Next.js (getStaticProps or similar)

If you're using this in a [slug].js file with Next.js, your query might look like:

export async function getStaticProps({ params }) {
  const query = `*[_type == "post" && slug.current == $slug][0] {
    title,
    body[]{
      ...,
      _type == "image" => {
        ...,
        asset->{
          _id,
          url,
          metadata {
            dimensions
          }
        }
      }
    }
  }`
  
  const post = await client.fetch(query, { slug: params.slug })
  
  return {
    props: { post }
  }
}

Rendering the Images

Once you have the expanded data, you can render images in your Portable Text using @portabletext/react:

import { PortableText } from '@portabletext/react'
import imageUrlBuilder from '@sanity/image-url'

const builder = imageUrlBuilder(client)

const components = {
  types: {
    image: ({ value }) => (
      <img 
        src={builder.image(value).width(800).url()} 
        alt={value.alt || ' '} 
      />
    )
  }
}

export default function Post({ post }) {
  return (
    <PortableText 
      value={post.body} 
      components={components} 
    />
  )
}

The key issue was that you need to explicitly expand the image asset references in your GROQ query using the asset-> syntax. Without this, you just get a reference object, not the actual image data you need to display the image.

Check out the official Sanity docs on Block Content image materializing for more details on this pattern!

Show original thread
25 replies
Hello! If I'm understanding correctly and you're trying to resolve a reference to an image inside of Block Content, you'll find this helpful. Particularly, you'll want to use this method for conditionally expanding references:
*[_type == "post"]{
  ...,
  body[]{
    ...,
    markDefs[]{
      ...,
      _type == "image" => {
        'asset': @.asset->
      }
    }
  }
}
Let me know if I got your goals wrong, though!
Aha. So this way the groq request exposes all _type == "image" objects in the body-array? .. I think I have to do some more reading on groq:)
Not quite getting the syntax right here... :
export async function getStaticPaths() {
  const paths = await client.fetch(
    groq
    `*[_type == "post" && defined(slug.current)][].slug.current`{
      body[]{
        ...,
        markDefs[]{
          ...,
          _type == "image" => {
            'asset': @.asset->
          }
        }
      }
    }
  )
This should do it then?
... not working..
You're passing that into your
getStaticPaths
function, so that's not going to pass any information to your component. That function just serves to prebuild all of the paths in your app.You'll want to use
getStaticProps
or
getServerSide
props.
Hahaha thanks! 🙂 My first time doing both Next and Sanity. Let's see if I can make it work now then .. .:)
user U
Speaking as a fellow learner, you're doing great so far picking up on stuff. I believe in you! 😃
I was super confused by these when I first started learning Next too! 🙂
haha I just explained the relationship between the static props and static paths functions to someone, or tried to, and as I taped the screencast I was thinking, "Boy this sounds like I am a rambling crazy person, yet I am professing the craziness with confidence as if any of this makes clear sense right out of the gate."
In my head I swear I get it, but articulating it is a whole extra effort. I am mostly getting by on my visual frames of reference and sense memory going through the same motions.

It doesn't help that it's not super obvious
how things are able to feed into each other, and what has access to what, when they're on the same "level" of alignment/nesting, in the same file, and where most examples/tutorials tuck all the stuff after you've said your important bits about what to do with information it looks like it couldn't know yet.
In this instance, I just did not see it/pick up on it, that I was in the slug query... too many new things going on at once and the brain starts filtering out some of the unknowns I guess 🙂
user U
I hear ya. I have tried unlocking my front door with my car keys....[checks notes]... four times this month.
I actually used that exact thing you're talking about for a speech class though. I wrote a beautiful essay, then looked at one sentence and memorized it and tried to read it off the top of my head. Then two sentences. Then three, etc.
I sort of naturally "lost" some stuff and I believe it was my brain realizing the flow or a given detail was kind of caca. So the version of the speech I managed to remember came off
really naturally because it was the way my own mind wanted to keep and compartmentalize it, and was leaner than if I rambled with my beautiful book of a first pass 😃
Now I'm getting "error - ClientError: unable to parse entire expression" - what is it I'm not seeing now?
Looks like a closure issue -- one more closing curly brace that is necessary. Unless my eyes are bad 😃
Still unable to log(image) in the getStaticProps function here.. ? One more hint?!
I log the object in the body by log(post), but I'm unable to do src={image.asset} ....
Are you trying to directly reference it? If it's an image inside a block, it should be able to be resolved with some of the tooling like this
I'm using React portable text. I just simply can not grab the in body/block image url..
Would you mind showing the piece of code on the lines where you're trying to bring them in? If they aren't processed automatically by the component the only thing I can think is that the image URL builder prefers when the image is still the whole image object and not dereferenced to its asset. And beyond that, just that if you're being specific about what you call it (so that the names match) when specifying how that kind of block is to render.
If you look under Src/pages/posts/[slug].js, I just uploaded it there: https://codesandbox.io/s/modest-montalcini-2cwzq8?file=/src/pages/posts/%5Bslug%5D.js
..just the one page. But should be sufficient.
I can't get the url into src on line 25. I've tried a few different methods. But I don't know how to return it from the getServersideProps to the document.
Body looks like this. But I can't pass the url.... I'm also wondering, if the serializer I've got for images in ptComponents, is fine - or if it only would work for one image. What would I do if I put several images in the text in Sanity Studio? Loop through inside ptComponents, and let the tags in ptComponents be the function?
Please have a look
user M
too, if you have the time:)
I am going to DM you something close to that which worked for me -- the top half is my Next code, and the bottom half, just for context, is the field that created the thing I am interpreting

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?