How to auto-populate excerpt field from blockContent in Sanity?

15 repliesLast updated: Dec 1, 2025

Hello there,I'm using Sanity for a blog, and I wonder if there's some way to create a field that gets populated with the text from another field (a blockContent).
Initial thought:

{

name: "excerpt",

title: "Excerpt",

type: "string",

options: {

source: "GET THE BLOCK CONTENT",

maxLength: 96,

},
Or can an excerpt get generated for the blockContent in an easier way?
(Sometimes the blogposts starts with a picture and sometimes with text, so I don't want to get the data by doing something static like
post.body[0]
AI Update

You definitely can auto-generate an excerpt from your blockContent field! The best approach is using the pt::text() function in your GROQ queries to extract plain text from Portable Text, which handles images, text blocks, and any other content automatically.

Instead of storing the excerpt as a separate field, generate it when you query your posts:

*[_type == "post"] {
  title,
  body,
  "excerpt": pt::text(body)[0..96]
}

The pt::text() function extracts all plain text from your Portable Text blocks, automatically handling whether your post starts with an image or text—exactly what you need! Then you can use array slicing ([0..96]) to limit it to the first 96 characters.

For a word-based excerpt instead of character-based, you can combine pt::text() with the string::split() function:

*[_type == "post"] {
  title,
  body,
  "excerpt": array::join(string::split(pt::text(body), " ")[0..20], " ")
}

This takes the first 20 words instead of cutting mid-word. Here's what's happening:

Schema-level initialValue (Alternative)

If you really want a stored excerpt field in your schema, you can use initialValue:

{
  name: "excerpt",
  title: "Excerpt",
  type: "string",
  initialValue: async ({document, getClient}) => {
    if (!document?.body) return '';
    
    const client = getClient({apiVersion: '2023-01-01'});
    const result = await client.fetch(
      `*[_id == $id][0]{"text": pt::text(body)}`,
      {id: document._id}
    );
    
    return result?.text?.substring(0, 96) || '';
  }
}

However, this approach is more complex and has downsides—the excerpt won't update automatically when the body changes, and you'd need to manually re-save documents to refresh it.

Why Query-time is Better

The query-time approach using pt::text() is simpler and more maintainable because:

The pt::text() function is specifically designed for this use case—extracting searchable, readable text from rich Portable Text content for things like excerpts, search indexing, and SEO meta descriptions.

Show original thread
15 replies

Was this answer helpful?

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.

Related contributions