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

Markdown to Portable Text

Convert Markdown strings into Portable Text blocks. Use this for importing content from Markdown-based systems (static site generators, GitHub READMEs, AI-generated content), processing user input, or migrating from Markdown-first CMSes.

Looking to render Portable Text as Markdown? See the Markdown rendering guide.

Terminal window
npm i @portabletext/markdown

markdownToPortableText takes a Markdown string and returns an array of Portable Text blocks.

import {markdownToPortableText} from '@portabletext/markdown'
const blocks = markdownToPortableText('# Hello **world**')

Standard Markdown elements (headings, paragraphs, bold, italic, links, lists, blockquotes, inline code) are handled automatically using the default schema.

Soft-wrapped lines within a paragraph join with a single space; a hard break (two or more trailing spaces, or a backslash, before the newline) becomes a line break within the block.

The conversion is schema-driven. The library only outputs types that exist in the schema, so the output always matches your content model.

The default schema includes:

Type Values
styles normal, h1-h6, blockquote
lists bullet, number, task
decorators strong, em, code, strike-through
annotations link (fields: href, title)
blockObjects code, image, horizontal-rule, html, table, callout
inlineObjects image

To use a custom schema, build one with compileSchema and defineSchema from @portabletext/schema and pass it as the schema option; a Sanity schema converts first through @portabletext/sanity-bridge. The package README has the configuration recipes for both.

Matchers control how Markdown elements map to schema types. The library includes defaults for all standard elements. You can override individual matchers when your schema uses different type names.

Group Matcher Markdown Maps to
block normal Paragraphs 'normal'
h1-h6 #-###### headings 'h1'-'h6'
blockquote > blockquotes 'blockquote'
listItem bullet - or * lists 'bullet'
number 1. ordered lists 'number'
marks strong **bold** 'strong'
em *italic* 'em'
code `inline code` 'code'
strikeThrough ~~strikethrough~~ 'strike-through'
link [text](url "title") 'link'
types code Fenced code blocks 'code'
horizontalRule --- 'horizontal-rule'
image ![alt](src) 'image'
html HTML blocks 'html'
callout > [!NOTE], etc. 'callout'

Override a matcher when your schema uses a different name for a type (say, 'heading 1' instead of 'h1'): pass your own function under the matcher’s group and name, resolve the type against context.schema, and return its name, or undefined to skip the element. The package README has worked matcher recipes, including the structural table, list, and blockquote shapes.

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

A construct the schema can’t represent (an undeclared decorator, a table with no table block object, and so on) degrades to a lossier shape by default rather than failing, silently: nothing reaches the console. onDegradation covers what an import pipeline or an agent that can’t afford to lose content silently needs: a function is called once, after the whole document has been walked, only when at least one construct degraded, with every Degradation (type, message, line when available, snippet when there’s source text to quote) in encounter order and a canonical grouped message, to observe the losses. Enforce against them by throwing your own error from inside that callback instead of returning lossy Portable Text; the throw propagates out of markdownToPortableText. The package README has the full option shape, an enforce example, and the report object’s fields.

Converting Markdown to Portable Text and back isn’t a lossless mirror: translation normalizes rather than preserves, and structures either side can’t express degrade predictably rather than failing. See Markdown round-tripping for the full contract, worked examples, and the named exceptions (linkified substrings, heading hard breaks, whitespace trimming).

Source format Tool
HTML → PT @portabletext/html
Gutenberg → PT @emdash-cms/gutenberg-to-portable-text (30+ block types)
Contentful → PT @portabletext/contentful-rich-text-to-portable-text