Show Portable Text in Preview When Title is Missing

16 replies
Last updated: May 30, 2022
This is my preview for the current object :
preview: {
    select: {
      title: "title",
      media: "image",
    },
    prepare({ title, media }) {
      return {
        title: title,
        subtitle: "Text and Image",
        media,
      };
    },
  },
Let's say I want to show the portable text if title is not added by the user.
AI Update

You can show the portable text content when the title is not provided by adding the portable text field to your select object and then conditionally using it in the prepare function. Here's how:

preview: {
  select: {
    title: "title",
    media: "image",
    portableText: "yourPortableTextField" // Add your portable text field name
  },
  prepare({ title, media, portableText }) {
    // If there's no title, extract text from portable text
    const displayTitle = title || 
      (portableText && portableText[0]?.children?.[0]?.text) || 
      'Untitled';
    
    return {
      title: displayTitle,
      subtitle: "Text and Image",
      media,
    };
  },
},

The key here is accessing the portable text content. Since portable text is an array of blocks, you need to drill into the structure to get the actual text. The pattern portableText[0]?.children?.[0]?.text gets the text from the first child of the first block.

If you want to be more robust and handle multiple blocks or extract more text, you could do something like:

prepare({ title, media, portableText }) {
  let displayTitle = title;
  
  if (!displayTitle && portableText) {
    // Get text from first block that has content
    const firstBlock = portableText.find(block => block._type === 'block');
    displayTitle = firstBlock?.children?.[0]?.text || 'Untitled';
  }
  
  return {
    title: displayTitle,
    subtitle: "Text and Image",
    media,
  };
},

This approach gives you a fallback chain: use the title if it exists, otherwise extract text from the portable text field, and finally fall back to "Untitled" if neither is available.

Show original thread
16 replies
I believe we automatically parse it to plain text when you don’t use the
prepare
function. The function behind the link will work. You can also
import { toPlainText } from '@portabletext/react'
…which is pretty much the same function, but maintained by us
Maybe we should mention it in the documentation then. 🙂 Might be better than people copying and pasting that function if they already use the React adapter.
or even better, we should include/expose the function that’s already in the studio 🙂
{
    name: "content",
    type: "array",
    title: "Article sections",
    of: [
      {
        name: "textBlock",
        type: "object",
        title: "Text block",
        fields: [
          {
            name: "portableText",
            title: "Portable Text",
            type: "portableText",
          },
        ],
      },
      {
        type: "textAndImage",
      },
      {
        type: "heroTextBlockAndImage",
      },
      {
        type: "textAndImageCTA",
      },
    ],
  },
This is inside my article.js document ☝️
Ah. Then you need to select the
content
field, and find the first
textBlock
and run its
portableText
array through the plaintext function inside
prepare
A quicker version is to select the first
textBlock
and the first paragraph in said
portableText
and take the text from there. It will probably work fine most of the time.
content
 .find(({_type}) => _type === "textBlock")
 ?.portableText
 ?.find(({_type}) => _type === "block")
 ?.children[0]
  .text

Feeling kinda stuping 😅
preview: {
            select: {
              title: "portableText",
            },
            prepare({ content }) {
              return {
                title: content
                  .find(({ _type }) => _type === "textBlock")
                  ?.portableText?.find(({ _type }) => _type === "block")
                  ?.children[0].text,
              };
            },
          },
You need to select that property.
You need to select that property. Right now,
preview.prepare()
only receives
title
because that’s what you defined in
preview.select
. If you want the property to be called
content
, you need to update
title: "portableText"
to
content: "portableText"
.
This is the error I get in the console
When using the code above ☝️
This got it working: 🙂
preview: {
            select: {
              title: "portableText",
            },
            prepare({ title }) {
              return {
                title: title[0].children[0].text,
              };
            },
          },
thanks
user F
!! 🙏

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?