Converting Markdown to Sanity: HTML intermediary vs direct conversion?

32 replies
Last updated: Jan 28, 2026
yeah the markdown process is quite daunting. Working on that now myself. I’m considering generating html from the markdown, then to Sanity but not quite sure if that’s better or not than trying to convert markdown directly. This is getting me much of the way: https://www.sanity.io/guides/how-to-migrate-your-html-blog-content-from-ghost
AI Update

I hear you—the markdown migration process can definitely be tricky! You're actually on a good path, and there are multiple approaches depending on your content structure.

Markdown → HTML → Portable Text vs. Direct Markdown → Portable Text

Both approaches work, but here's what to consider:

Going the HTML route (what you're exploring):

  • Use @sanity/block-tools with the htmlToBlocks function
  • This is actually a solid approach because HTML is more structured than markdown
  • The Ghost HTML migration guide you linked uses this pattern and it's battle-tested
  • The package handles basic rich text formatting (headings, paragraphs, lists) automatically

Direct markdown route:

  • You'd need to parse markdown to MDAST (Markdown Abstract Syntax Tree) using something like remark
  • Then write custom transformation logic to convert MDAST to Portable Text blocks
  • There isn't a well-maintained "markdown-to-portable-text" package in the ecosystem
  • The @portabletext packages mostly work in reverse (Portable Text → other formats)

My recommendation: Stick with the HTML approach you're exploring. Here's why:

  1. Better tooling support: @sanity/block-tools has proven utilities for HTML → Portable Text conversion
  2. Easier to handle complex content: Markdown parsers vary in how they handle edge cases, but HTML is more standardized
  3. Custom deserialization rules: You can define custom rules to handle specific HTML elements like <figure> or <img> tags and transform them into custom block types
  4. The Ghost guide pattern works: Convert markdown → HTML (using marked, remark-html, or similar), then use htmlToBlocks

Quick implementation pattern:

import {htmlToBlocks} from '@sanity/block-tools'
import {JSDOM} from 'jsdom'
import {marked} from 'marked' // or your preferred markdown parser

// Convert markdown to HTML first
const html = marked(yourMarkdownContent)

// Then to Portable Text
const blocks = htmlToBlocks(html, blockContentType, {
  parseHtml: html => new JSDOM(html).window.document
})

Pro tips:

  • Test with your most complex markdown first (nested lists, code blocks, images, inline styles)
  • You may need custom deserialization rules for specific markdown features
  • Keep your original markdown files as backup during migration
  • Remember that htmlToBlocks is synchronous, so you'll need to handle image uploads and asset references in a separate post-processing step

The two-step process (markdown → HTML → Portable Text) gives you more control and better debugging when things don't convert perfectly. Good luck with your migration!

Show original thread
32 replies

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?