Skip to content
This page is available as Markdown at /rendering/markdown.md. For the full documentation index, see /llms.txt, or the complete corpus at /llms-full.txt.

Markdown

@portabletext/markdown renders Portable Text blocks as Markdown strings. Use it to generate Markdown for static site generators, README files, AI prompts, or any system that consumes Markdown.

Looking to convert Markdown into Portable Text? See the Markdown to Portable Text conversion guide, whose Round-trip behavior section covers what survives a Markdown → Portable Text → Markdown loop.

Terminal window
npm i @portabletext/markdown

portableTextToMarkdown takes an array of Portable Text blocks and returns a Markdown string. Standard block styles, decorators, and links are handled automatically.

import {portableTextToMarkdown} from '@portabletext/markdown'
const markdown = portableTextToMarkdown(blocks)

callout, code, horizontal-rule, html, image, and table block objects render as Markdown out of the box. callout, code, html, image, and table fall back to a fenced JSON block when a value doesn’t match the shape its renderer expects (a consumer’s own differently-shaped code type, say); horizontal-rule always renders ---. Register a renderer for other custom types you use, or to override one of the defaults.

portableTextToMarkdown(blocks, {
types: {
chart: ({value}) => `![${value.title}](${value.imageUrl})`,
},
})

Type renderers receive value (the block object), index (position in the array), and isInline (whether the object appears inline or as a block). Return an empty string to skip an element entirely.

portableTextToMarkdown(blocks, {
types: {
image: ({value, isInline}) => {
if (isInline) return ''
return `![${value.alt || ''}](${value.src})`
},
},
})

Override how block styles render by providing a block map. Each renderer receives value (the block) and children (the already-rendered content of the block).

portableTextToMarkdown(blocks, {
block: {
h1: ({children}) => `# ${children} #`,
blockquote: ({children}) => `<blockquote>${children}</blockquote>`,
},
})

callout, code, horizontal-rule, html, image, and table are registered by default. The package also exports DefaultBlockquoteObjectRenderer and DefaultListRenderer for the structural container shapes the parser produces when types.blockquote/types.list matchers are registered; those two stay opt-in since the parser only produces that shape when asked to.

import {
DefaultBlockquoteObjectRenderer,
DefaultListRenderer,
portableTextToMarkdown,
} from '@portabletext/markdown'
portableTextToMarkdown(blocks, {
types: {
blockquote: DefaultBlockquoteObjectRenderer,
list: DefaultListRenderer,
},
})

Supply your own types.<name> renderer to override any of the defaults.

Renderer Expected fields Output
DefaultCalloutRenderer tone, content > [!TYPE]\n> content
DefaultCodeBlockRenderer code, language? ```lang\ncode\n```
DefaultHorizontalRuleRenderer (none required) ---
DefaultHtmlRenderer html Raw HTML string
DefaultImageRenderer src, alt?, title? ![alt](src "title")
DefaultTableRenderer rows, headerRows? Markdown table
DefaultBlockquoteObjectRenderer content > content
DefaultListRenderer kind, items Markdown list
Key What it renders
types Custom block and inline objects
marks Annotations and decorators
block Block styles (headings, blockquotes, etc.)
listItem List items
hardBreak Line breaks within blocks
unknownType Fallback for unregistered types
unknownBlockStyle Fallback for unregistered block styles
unknownListItem Fallback for unregistered list items
unknownMark Fallback for unregistered marks

By default, unknown types render as JSON code blocks. Unknown marks, block styles, and list items pass through their children unchanged.

Feature PT to Markdown
Headings (h1-h6)
Paragraphs
Bold
Italic
Inline code
Strikethrough
Links
Blockquotes
Ordered lists
Unordered lists
Nested lists
Code blocks
Horizontal rules
Images
Tables
HTML blocks
Callouts