Subchapter 6.5
references/markdown.mdMarkdown5 KBView on GitHub
Before writing migration code, determine:
Use direct Markdown -> Portable Text conversion with @portabletext/markdown.
npm install @portabletext/markdownimport {markdownToPortableText} from '@portabletext/markdown'
const body = markdownToPortableText(markdown)Do not call Markdown -> HTML -> htmlToBlocks “direct conversion.” That is an HTML fallback path. Use it only when the Markdown pipeline cannot handle source-specific syntax that is easier to normalize as HTML.
For a Sanity block array schema, convert it with @portabletext/sanity-bridge:
npm install @portabletext/markdown @portabletext/sanity-bridgeimport {markdownToPortableText} from '@portabletext/markdown'
import {sanitySchemaToPortableTextSchema} from '@portabletext/sanity-bridge'
const schema = sanitySchemaToPortableTextSchema(sanityBlockArraySchema)
const body = markdownToPortableText(markdown, {schema})Use custom matchers when Markdown syntax must become specific Sanity objects:
const body = markdownToPortableText(markdown, {
schema,
types: {
table: ({context, value}) => ({
_type: 'table',
_key: context.keyGenerator(),
rows: value.rows,
headerRows: value.headerRows,
}),
},
})Only emit custom types that exist in the target schema.
Use a frontmatter parser such as gray-matter:
npm install gray-matterMap common fields deliberately:
title -> document title.slug or file path -> slug.current.date, published, updated -> explicit datetime fields such as publishedAt.author -> reference, string, or author document depending on target schema.tags and categories -> reference documents when they power filtering/navigation.description, canonical, ogImage -> SEO object if the target schema has one.Do not put frontmatter into the Portable Text body unless it is actual editorial content.
Markdown images need explicit handling:
_sanityAsset: "image@https://..." in NDJSON or upload through the client.file:///... URI in NDJSON, package assets into a tarball, or upload through the client. and title text when present.If images should remain inline inside Portable Text, ensure the Portable Text schema allows an image object. If images belong in a separate field such as mainImage, extract them before conversion.
Use stable IDs:
post-my-folder-my-file.post-<sourceId>.For bulk imports, prefer NDJSON:
{"_id":"post-example","_type":"post","title":"Example","body":[{"_type":"block","_key":"a","style":"normal","children":[{"_type":"span","_key":"b","text":"Hello","marks":[]}],"markDefs":[]}]}Then import with:
npx sanity datasets import import.ndjson <dataset> --replaceUse client createOrReplace for smaller imports or incremental syncs.
Only keep Markdown native when the project intentionally wants Markdown editing/rendering instead of Portable Text querying and structured blocks.
If keeping Markdown native, use a Markdown field/plugin and document the tradeoff: simpler migration and editing for Markdown-first teams, but weaker structured querying, references, annotations, and block-level content reuse.
_id, _type, title/slug where required, and a Portable Text array._type values exist in the schema.