How to fix "Unknown block type undefined" error in Portable Text with Sanity?

16 replies
Last updated: Dec 21, 2022
Hello, I am fetching a doc like this :
const main = await sanity.fetch(groq
    `*[_type == "pages"][pageSlug.current == $pageSlug][0]{
      'hero': blockContent[_type == 'hero'],
      'platforms': blockContent[_type == 'platforms']
    }
    `,{pageSlug}
    )
I am using Portable Text to display components,

const ptComponents = {
    // Components for totally custom types outside the scope of Portable Text
    types: {
        hero: Main,
        platforms: Platforms
    },
  
    // Overrides for specific block styles - in this case just the `h2` style
    block: {
     
    },
  
    // Implements a custom component to handle the `schnauzer` list item type
    list: {
     
    },
  
    // Custom components for marks - note that `link` overrides the default component,
    // while the others define components for totally custom types.
    marks: {

    },
  }

<PortableText components={ptComponents} value={props.main} />
Eventually I get this error

[@portabletext/react] Unknown block type "undefined", specify a component for it in the `components.types` prop
How can I fix this please??
AI Update

The error you're seeing happens because your GROQ query is returning an object structure that doesn't match what Portable Text expects. The issue is in how you're filtering your blockContent array.

When you do 'hero': blockContent[_type == 'hero'], this returns an array of hero blocks, not the blocks themselves in Portable Text format. Then when you pass props.main to <PortableText>, it's receiving an object like { hero: [...], platforms: [...] } rather than a proper Portable Text array of blocks.

Here are two ways to fix this:

Option 1: Pass the arrays separately (recommended)

Instead of passing the entire main object to a single PortableText component, render each section separately:

const main = await sanity.fetch(groq`
  *[_type == "pages" && pageSlug.current == $pageSlug][0]{
    'hero': blockContent[_type == 'hero'],
    'platforms': blockContent[_type == 'platforms']
  }
`, {pageSlug})

// Then render:
<>
  <PortableText components={ptComponents} value={main.hero} />
  <PortableText components={ptComponents} value={main.platforms} />
</>

Option 2: Keep the full blockContent structure

If you want to render everything together in order, fetch the entire blockContent array and let PortableText handle it:

const main = await sanity.fetch(groq`
  *[_type == "pages" && pageSlug.current == $pageSlug][0]{
    blockContent
  }
`, {pageSlug})

// Then render:
<PortableText components={ptComponents} value={main.blockContent} />

The key issue is that Portable Text expects an array of blocks, not an object with nested arrays. Each block in the array should have a _type property that matches your component definitions (hero, platforms, etc.).

Also note: Your GROQ filter syntax should use && instead of chaining multiple [][]. The corrected syntax is *[_type == "pages" && pageSlug.current == $pageSlug][0].

Show original thread
16 replies
This might help: Disabling warnings / handling unknown types

To disable this behavior, you can either pass
false
to the
onMissingComponent
property, or give it a custom function you want to use to report the error.
user J
Thank you! But I am not sure how can I do it in my case 😅
The issue is coming from the props
main
, this is the array I am passing
So somewhere in your portable text is a block type you did not define.If you read through the ReadMe this error about undefined type is mentioned, but wait...
I am also wondering, why you are querying for your block content in the way you do…
This way you do not get an array of blocks as an output but just objects…
(as you just posted here )
If I put in the values something like
props.main.hero
the hero component will be displayed
Perhaps the way I defined the query isnt helpful for this case
this
const main = await sanity.fetch(groq
    `*[_type == "pages"][pageSlug.current == $pageSlug][0]{
      'hero': blockContent[_type == 'hero'],
      'platforms': blockContent[_type == 'platforms']
    }
    `,{pageSlug}
    )

This is the way you need to query for portable text (this is a different example then yours but you will get the main idea)
This is another example of one of my own portable text queries:

const query = `[]{body[]{ ...,  
    _type == "file" => {..., "url": @.asset->url},  
    _type == "internalBtn" => { 
        "reference": @.reference->_type,
        "slug": "/"+  @.reference-> slug.current },
    _type == "testimonial" => {..., "reference": @->}, 
    markDefs[]{ 
        ..., 
        _type == "internalLink" => { "slug": "/"+ @.reference-> slug.current },
        _type == "publication" =>  {..., "reference": @->{url, title, description, "file": @.file.asset->url } },
        _type == "job" =>  {"reference": @->{url, title, description, location} }
        },
    }`;
This is another example of one of my own portable text queries:

const query = `*[_type == 'pages' && pageSlug.current == $pageSlug][0]{
...,
  body[]{ 
     ...,  
    _type == "file" => {..., "url": @.asset->url},  
    _type == "testimonial" => {..., "reference": @->}, 
    markDefs[]{ 
        ..., 
        _type == "internalLink" => { "slug": "/"+ @.reference-> slug.current },
        _type == "publication" =>  {..., "reference": @->{url, title, description, "file": @.file.asset->url } },
        _type == "job" =>  {"reference": @->{url, title, description, location} }
        },
    }`;

Oh I understand, I am just curious, I think I shouldn't add dynamic Components this way? Initially this object I am trying to display in the frontend is not coming from a portable text editor, instead it's an array of different types
hero
and
platforms
then you can pass the body array to the PortableText component like this
<PortableText components={PTcomponents} value={props.body} />
But you need to define what to serialise for each value like file in that case:

const PTcomponents = {
    types: {
      file: (props) => {
        return (
          <Button
            href={props.value.url + "?dl"}
            color={color || "carrot"}
            className={ButtonClassName + " mr-2"}
            border={borderInternal}
          >
            {props.value.title || "Download"}
          </Button>
        );
      },
//YOUR OTHER SERIALIZERS AND MARKS 
marks: {
      internalLink: ({value, children}) => {
        return (
          <Link href={value.slug}>
            <a className="underline">
              {children}{" "}
              <BsSignpost className="inline mb-1 text-grey opacity-30" />
            </a>
          </Link>
        );
      },
}}}
Wow, thats really helpful, truly thankful!!
wait one edit ☝️
Happy I could help!

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?