
Portable Text Serialization
Render Sanity Portable Text to React, Svelte, Vue, Astro, HTML, Markdown, or plain text with correct marks, lists, and custom block types.
Overview
portable-text-serialization is an agent skill for the Build phase that guides Portable Text rendering and serialization across frameworks and export formats using @portabletext/*.
Install
npx skills add https://github.com/sanity-io/agent-toolkit --skill portable-text-serializationWhat is this skill?
- Covers React, Svelte, Vue, Astro, HTML, Markdown, and plain-text extraction via @portabletext/* libraries
- Unified components-mapping pattern for blocks, spans, marks, lists, and custom _type blocks
- Quick-reference PT structure: style, children, markDefs, listItem, level for nested lists
- Troubleshooting guidance for marks, non-standard block types, and custom serializers
- Documents 7+ output targets: React, Svelte, Vue, Astro, HTML, Markdown, and plain text
Adoption & trust: 831 installs on skills.sh; 150 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You have Portable Text from Sanity but marks, custom blocks, or list nesting render wrong—or you need HTML, Markdown, or plain text without guessing serializer APIs.
Who is it for?
Builders using Sanity CMS who need multi-framework PT rendering or server-side string export.
Skip if: Teams not on Portable Text or projects that only need raw JSON with no presentation layer.
When should I use this skill?
Implementing Portable Text rendering in any frontend framework, custom serializers, HTML/Markdown/plain-text conversion, or troubleshooting marks, blocks, lists, or custom types.
What do I get? / Deliverables
You implement a correct components map and serializers so PT displays in your framework and converts cleanly to HTML, Markdown, or plain text.
- Framework-specific PT renderer configuration
- Optional HTML/Markdown/plain-text conversion pipeline
Recommended Skills
Journey fit
Serialization and framework renderers are implemented while building the product UI and content surfaces. Portable Text component maps and @portabletext/* usage are frontend implementation work, including server-side HTML/Markdown conversion.
How it compares
Framework serialization recipes for Sanity PT, not a headless CMS setup skill or generic Markdown converter.
Common Questions / FAQ
Who is portable-text-serialization for?
Solo and indie frontend developers integrating Sanity Portable Text into React, Svelte, Vue, Astro, or server HTML/Markdown pipelines.
When should I use portable-text-serialization?
Use it during Build/frontend when implementing PT rendering, custom block serializers, server-side HTML conversion, Markdown export, or debugging marks and lists.
Is portable-text-serialization safe to install?
It is MIT-licensed Sanity documentation; review the Security Audits panel on this Prism page before installing the skill in your agent.
SKILL.md
READMESKILL.md - Portable Text Serialization
# 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", "<markDefKey>"] } │ ├── 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.md` — `astro-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](https://www.portabletext.org/integrations/serializers/) 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 war