How to auto-populate excerpt field from blockContent in Sanity?
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.
Query-time Extraction (Recommended)
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:
pt::text(body)extracts plain text from your Portable Textstring::split(pt::text(body), " ")splits it into an array of words[0..20]takes the first 20 wordsarray::join(..., " ")joins them back into a string with spaces
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:
- It always reflects the current content (no sync issues)
- Handles images, embeds, and text blocks automatically—no need to worry about
post.body[0]being an image - No need to update the excerpt when body changes
- Works with GROQ's string and array manipulation functions for flexible text processing
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 thread15 replies
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.