Sanity Portable Text vs. plain text fields: a content modeling decision
Decide when to use Sanity Portable Text instead of a plain string field for rich content, with schema, query, and rendering guidance.
17 Sept 2026, 21:28 UTC

The decision: plain string or Portable Text?
If your Sanity schema needs rich content—headings, bold text, links, or embedded images—you have two supported starting points: a plain string field or Sanity Portable Text. Portable Text is Sanity's structured rich-text model; it stores content as a JSON array of block objects rather than a single string. The wrong choice forces editors to work around the field or forces developers to maintain a custom serializer later.
Constraints to set before choosing:
- Editorial: do writers need nested lists, block quotes, or inline annotations?
- Frontend: can your rendering stack consume a block array, or does it expect HTML?
- Querying: will you filter or project inside the rich text with GROQ?
- Migration: are you converting existing content from another CMS?
Supported options compared
| Option | Best for | GROQ complexity | Frontend rendering | Migration risk |
|---|---|---|---|---|
| Plain string | Short titles, captions, or plain biographies with no formatting | Low; project the field directly | Display as-is or escape to HTML | Low; no structure to change |
| Portable Text block array | Articles, pages, and fields that need headings, lists, links, and images | Medium; project block children and marks | Requires a Portable Text serializer for React, Vue, or plain HTML | Medium; existing string data must be mapped into blocks |
| Portable Text with custom annotations | Editorial links to internal documents, product mentions, or callout boxes | Higher; filter by annotation type in projections | Serializer must handle custom mark types | Medium; annotation schema changes affect old documents |
Trade-offs to weigh
Plain strings are simple to query, but they cannot represent bold ranges or embedded assets without inventing your own markup convention. That convention becomes a parsing burden on the frontend.
Portable Text keeps formatting explicit in JSON, so multiple editors can collaborate in real time with conflict resolution at the block level. The cost is a serializer layer: your frontend must map block types, styles, and marks to components. Sanity does not render Portable Text for you outside the Studio.
Custom annotations extend Portable Text, but every annotation type is a contract between the schema and the frontend. Changing an annotation later requires updating both sides and checking older documents.
Implementation: add a Portable Text field
This assumes the Sanity Studio schema format using defineType and defineField. Run the following in your Sanity Studio directory. You need a project role with schema edit permissions and, if deploying through the CLI, an API token with write access to the dataset.
// schemas/blockContent.ts
import { defineType, defineField } from 'sanity';
export const blockContent = defineType({
title: 'Body',
name: 'body',
type: 'array',
of: [
{
type: 'block',
title: 'Block',
styles: [
{ title: 'Normal', value: 'normal' },
{ title: 'Heading 2', value: 'h2' },
{ title: 'Heading 3', value: 'h3' },
{ title: 'Quote', value: 'blockquote' },
],
lists: [{ title: 'Bullet', value: 'bullet' }],
marks: {
decorators: [
{ title: 'Strong', value: 'strong' },
{ title: 'Emphasis', value: 'em' },
],
annotations: [
{
name: 'link',
type: 'object',
title: 'Link',
fields: [
{ name: 'href', type: 'url', title: 'URL' },
],
},
],
},
},
{
type: 'image',
title: 'Image',
options: { hotspot: true },
},
],
});
Attach this type to a document schema, then run sanity deploy from the Studio directory to push the schema to your dataset. Replace your-dataset with the dataset name if your CLI configuration requires it.
Expected check: open a document in Sanity Studio. The body field should show a block editor where you can insert headings, bullet lists, links, and images. Existing documents created before the deploy will not automatically contain Portable Text data.
Risk: deploying a schema change modifies the dataset schema. If a field name collides with an existing field, the deploy may fail or overwrite behavior. Verify field names in sanity.json and your schema files before deploying.
Validate the content with GROQ
Use Sanity's Vision tool or API playground to inspect how Portable Text projects. This query fetches the title and body blocks for documents of type post:
*[_type == "post"] {
title,
body[] {
_type,
style,
markDefs[],
children[] {
_type,
text,
marks[]
}
}
}
Expected check: each block should return _type: "block" with a children array. Text marks appear in marks, and link annotations appear in markDefs. If the field is empty or returns strings, the schema may not be deployed to the dataset you are querying.
Practical way to check the result: publish a document with one heading block and one link block, then run the query against that document's ID. Confirm the response contains the block structure before building the frontend serializer.
Limitations
- Portable Text is not HTML. Your frontend still needs a serializer to convert blocks, marks, and annotations into components.
- Real-time collaboration depends on WebSocket connectivity between the Studio and Sanity's backend; unstable networks may delay presence and conflict updates.
- Image variants processed through the Sanity CDN can increase usage costs on high-traffic sites.
- Schema edits require developer coordination; content editors cannot add block styles or annotation types without a schema deploy.
Rollback
Because deploying a schema changes dataset state, rollback means reverting the schema file to its previous version and running sanity deploy again. Documents that already contain Portable Text data will keep that data unless you explicitly remove the field, so plan a data migration if you are replacing an existing string field.
Verification checklist
- Studio reloads after deploy and shows the block editor.
- GROQ projection returns
_type: "block"entries withchildren. - Frontend serializer renders at least one block style and one annotation without crashing.
- Old documents created before the schema change remain readable in the Studio.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.