Labsco
sanity-io logo

portable-text-serialization

160

by sanity-io · part of sanity-io/agent-toolkit

Render and serialize Portable Text to React, Svelte, Vue, Astro, HTML, Markdown, and plain text. Use when implementing Portable Text rendering in any frontend…

🔥🔥🔥✓ VerifiedFreeQuick setup
🧩 One of 7 skills in the sanity-io/agent-toolkit package — works on its own, and pairs well with its siblings.

This is the playbook your agent receives when the skill activates — you don't need to read it to use the skill, but it's here to audit before installing.

by sanity-io

Render and serialize Portable Text to React, Svelte, Vue, Astro, HTML, Markdown, and plain text. Use when implementing Portable Text rendering in any frontend… npx skills add https://github.com/sanity-io/agent-toolkit --skill portable-text-serialization Download ZIPGitHub160

Portable Text Serialization

Render Portable Text content across frameworks using the @portabletext/* library family. Each library follows the same component-mapping pattern: you provide a components object that maps PT node types to framework-specific renderers.

Portable Text Structure (Quick Reference)

PT is an array of blocks. Each block has _type, optional style, children (spans), markDefs, listItem, and level.

Root array
├── block (_type: "block")
│ ├── style: "normal" | "h1" | "h2" | "blockquote" | ...
│ ├── children: [span, span, ...]
│ │ └── span: { _type: "span", text: "...", marks: ["strong", " "] }
│ ├── markDefs: [{ _key, _type: "link", href: "..." }, ...]
│ ├── listItem: "bullet" | "number" (optional)
│ └── level: 1, 2, 3... (optional, for nested lists)
├── custom block (_type: "image" | "code" | any custom type)
└── ...more blocks

Marks come in two forms:

  • Decorators: string values in marks[] like "strong", "em", "underline", "code"

  • Annotations: keys in marks[] referencing entries in markDefs[] (e.g., links, internal references)

Component Mapping Pattern (All Frameworks)

Every @portabletext/* library accepts a components object with these keys:

Key Renders Props/Data types Custom block/inline types (image, code, CTA) value (the block data) marks Decorators + annotations children + value (mark data) block Block styles (h1, normal, blockquote) children list List wrappers (ul, ol) children listItem List items children hardBreak Line breaks within a block —

Framework-Specific Rules

Read the rule file matching your framework:

  • React / Next.js: rules/react.md@portabletext/react or next-sanity

  • Svelte / SvelteKit: rules/svelte.md@portabletext/svelte

  • Vue / Nuxt: rules/vue.md@portabletext/vue

  • Astro: rules/astro.mdastro-portabletext

  • HTML (server-side): rules/html.md@portabletext/to-html

  • Markdown: rules/markdown.md@portabletext/markdown

  • Plain text extraction: rules/plain-text.md@portabletext/toolkit

Additional Community Serializers

These are listed on portabletext.org but don't have dedicated rule files:

Target Package React Native @portabletext/react-native-portabletext React PDF @portabletext/react-pdf-portabletext Solid solid-portabletext Qwik portabletext-qwik Shopify Liquid portable-text-to-liquid PHP sanity-php (SanityBlockContent class) Python portabletext-html C# / .NET dotnet-portable-text Dart / Flutter flutter_sanity_portable_text

Common Patterns (All Frameworks)

Custom Types Need Explicit Components

PT renderers only handle standard blocks by default. Custom types (image, code, callToAction, etc.) require explicit component mappings — they won't render otherwise.

Keep Components Object Stable

In React/Vue, define components outside the render function or memoize it. Recreating on every render causes unnecessary re-renders.

Handle Missing Components Gracefully

All libraries accept onMissingComponent to control behavior when encountering unknown types:

  • false — suppress warnings

  • Custom function — log or report

Querying PT with GROQ

Always expand references inside custom blocks:

body[]{
 ...,
 _type == "image" => {
 ...,
 asset->
 },
 markDefs[]{
 ...,
 _type == "internalLink" => {
 ...,
 "slug": @.reference->slug.current
 }
 }
}