All skills
sanity-io avatar

/portable-text-serialization

@a11c399 official
by Sanitysanity-io/agent-toolkit187 stars
30

Render and serialize Portable Text to React, Svelte, Vue, Astro, HTML, Markdown, and plain text. Use when implementing Portable Text rendering in any frontend framework, building custom serializers for non-standard block types, converting Portable Text to HTML strings server-side, converting Portable Text to Markdown, extracting plain text from Portable Text, or troubleshooting rendering issues with marks, blocks, lists, or custom types.

Use this Skill: https://skilld.dev/gh/sanity-io/agent-toolkit/portable-text-serialization

This session only. Nothing lands on disk.

rulesmarkdown.md

≈980 tokens on demand. Your agent reads this file only when SKILL.md points to it.

Serialize Portable Text to Markdown

Use @portabletext/markdown to convert PT blocks to Markdown strings. Useful for AI/LLM pipelines, static site generators, README generation, and anywhere Markdown is the target format.

npm install @portabletext/markdown

Basic Usage

import {portableTextToMarkdown} from '@portabletext/markdown'

const markdown = portableTextToMarkdown(portableTextBlocks)

Built-in Support

Out of the box, portableTextToMarkdown handles:

  • Headings (h1–h6)
  • Paragraphs
  • Bold (**), italic (_), inline code (`), strikethrough (~~)
  • Links ([text](url))
  • Blockquotes (>)
  • Ordered and unordered lists (including nested)
  • Code blocks (fenced with language)
  • Horizontal rules (---)
  • Images (![alt](url))
  • Tables (GFM)

Built-in Type Renderers

The library exports default renderers for common block object types. Enable them explicitly:

import {
  portableTextToMarkdown,
  DefaultCodeBlockRenderer,
  DefaultImageRenderer,
  DefaultHorizontalRuleRenderer,
  DefaultTableRenderer,
  DefaultHtmlRenderer,
} from '@portabletext/markdown'

const markdown = portableTextToMarkdown(blocks, {
  types: {
    'code': DefaultCodeBlockRenderer,           // {code, language?} → fenced code block
    'image': DefaultImageRenderer,              // {src, alt?, title?} → ![alt](src "title")
    'horizontal-rule': DefaultHorizontalRuleRenderer, // → ---
    'table': DefaultTableRenderer,              // {rows, headerRows?} → GFM table
    'html': DefaultHtmlRenderer,                // {html} → raw HTML
  },
})

Custom Renderers

Handle custom block types and marks with renderer functions:

const markdown = portableTextToMarkdown(blocks, {
  // Custom block types — receives {value, index, isInline}
  types: {
    callout: ({value}) => `> **${value.title}**\n> ${value.text}`,
    image: ({value, isInline}) => {
      if (isInline) return ''
      return `![${value.alt || ''}](${value.url})`
    },
  },

  // Custom block style renderers — receives {value, children, index}
  block: {
    h1: ({children}) => `# ${children}`,
    blockquote: ({children}) => `> ${children}`,
  },

  // Custom mark renderers — receives {value, children, text, markType, markKey}
  marks: {
    highlight: ({children}) => `==${children}==`,
    internalLink: ({children, value}) => `[${children}](/docs/${value.slug})`,
  },

  // Custom list item renderer — receives {value, children, listIndex}
  listItem: ({children}) => children,

  // Control spacing between blocks — function, not string
  blockSpacing: ({current, next}) => {
    if (current.listItem && next.listItem) return '\n'
    return undefined // use default (\n\n)
  },

  // Handle unknown types gracefully
  unknownType: ({value}) => `<!-- Unknown type: ${value._type} -->`,
  unknownMark: ({children}) => children,
})

Use Cases

Use Case Why Markdown
AI/LLM context Models work well with Markdown input
Static site generators Hugo, Jekyll, Eleventy consume Markdown
README generation Generate docs from Sanity content
Email (with converter) Markdown → HTML for email templates
Export/backup Human-readable content export
Documentation pipelines Sanity as docs CMS, output as Markdown

Bidirectional: Also Converts Markdown → PT

The same package also provides markdownToPortableText() for the reverse direction. See the portable-text-conversion skill for details.

Reference

Source: SKILL.md on GitHub

No alerts16d4 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides comprehensive reference guides, code patterns, and best practices for serializing and rendering Portable Text across several frontend frameworks (React, Svelte, Vue, Astro, HTML, Markdown). It implements secure coding recommendations, explicitly advising developers on how to prevent Cross-Site Scripting (XSS) vulnerabilities by sanitizing data and escaping HTML during server-side string generation.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at a11c399. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 weeks ago.

Activeupdated 8 months ago
metadata
{
  "author": "sanity",
  "version": "1.0.0"
}
  • React
  • Vue
  • portable-text
  • sanity
  • svelte
  • astro
  • serialization
  • rendering
  • markdown
  • html

README badge

README badge for sanity-io/agent-toolkit/portable-text-serialization

Render Portable Text to React, Svelte, Vue, Astro, HTML, Markdown, and plain text using the @portabletext library family. Maps PT node types (blocks, marks, custom types) to framework-specific components, with rules and patterns for each target framework and guidance on custom serializers for non-standard block types.

Generated from the current SKILL.md.

Does this skill support rendering Portable Text in React, Vue, Svelte, and Astro?
Yes. The skill covers @portabletext/react, @portabletext/vue, @portabletext/svelte, and astro-portabletext, each following the same component-mapping pattern.
Can I convert Portable Text to HTML or Markdown server-side?
Yes. Use @portabletext/to-html for server-side HTML strings or @portabletext/markdown to convert to Markdown format.
How do I render custom block types like images or code blocks?
Custom types require explicit component mappings in the components object — they will not render by default. Map each custom _type to a renderer that receives the block's value as props.
Does this skill help with plain text extraction?
Yes. Use @portabletext/toolkit to extract plain text from Portable Text content.
What if my framework or target (React Native, Solid, PHP) isn't listed in the rules?
The skill documents the core component-mapping pattern used by all serializers and lists community packages for React Native, Solid, PHP, Python, and other languages on portabletext.org, though dedicated rule files are only provided for React, Vue, Svelte, Astro, HTML, and Markdown.

Generated from the current SKILL.md. These answers refresh after source changes.