Skip to content

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.

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)

Custom block types (objects in the blocks array) are not rendered by default. Register a renderer for each type you use.

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>`,
},
})

The package exports default renderers for common block types. Import and register the ones you need.

import {
DefaultCalloutRenderer,
DefaultCodeBlockRenderer,
DefaultHorizontalRuleRenderer,
DefaultHtmlRenderer,
DefaultImageRenderer,
DefaultTableRenderer,
portableTextToMarkdown,
} from '@portabletext/markdown'
portableTextToMarkdown(blocks, {
types: {
'callout': DefaultCalloutRenderer,
'code': DefaultCodeBlockRenderer,
'horizontal-rule': DefaultHorizontalRuleRenderer,
'html': DefaultHtmlRenderer,
'image': DefaultImageRenderer,
'table': DefaultTableRenderer,
},
})
RendererExpected fieldsOutput
DefaultCalloutRenderertone, content> [!TYPE]\n> content
DefaultCodeBlockRenderercode, language?```lang\ncode\n```
DefaultHorizontalRuleRenderer(none required)---
DefaultHtmlRendererhtmlRaw HTML string
DefaultImageRenderersrc, alt?, title?![alt](src "title")
DefaultTableRendererrows, headerRows?Markdown table
KeyWhat it renders
typesCustom block and inline objects
marksAnnotations and decorators
blockBlock styles (headings, blockquotes, etc.)
listItemList items
hardBreakLine breaks within blocks
unknownTypeFallback for unregistered types
unknownBlockStyleFallback for unregistered block styles
unknownListItemFallback for unregistered list items
unknownMarkFallback for unregistered marks

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

FeaturePT 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✅*

* Requires registering the built-in renderer (see above).