ConverterMarch 28, 2026 · 10 min read

Markdown to HTML: Convert Without Losing Formatting

Markdown looks like one language but is actually five overlapping ones. Here is the converter workflow that survives the round-trip into Substack, Notion, Gmail, or your CMS.

You wrote a draft in Markdown — in Notion, in Obsidian, in your README, in a comment block — because Markdown is what writers and developers actually type. Now you need HTML. To paste into Substack. To send a styled email. To feed a CMS that does not understand Markdown. The conversion is one click; the reasons it sometimes goes wrong are worth knowing.

This guide covers the Markdown → HTML conversion that actually preserves what you wrote — including the parts that break between flavors (tables, task lists, footnotes), and the round-trip from HTML back to Markdown that almost always loses something. The fast path: paste into the converter, copy the output, paste into the destination. The rest is for when that does not work the way you expected.

The fastest path is at the top, the gotchas come after. Skip ahead to the section that matches your destination — Gmail, Substack, Notion, Medium, or a static site generator — if the simple workflow already worked.

Free Tool
Open the Markdown to HTML converter (free, in-browser)
Convert Markdown to HTML in your browser. Free online Markdown to HTML converter with GFM support, code blocks, and tables.

Why round-trips lose information

Markdown is not one language. It is at least five overlapping dialects, and a converter that targets one will silently drop features from another:

  • **CommonMark**: the standardised core. Headings, lists, links, emphasis, code blocks. No tables, no task lists, no footnotes.
  • **GitHub Flavored Markdown (GFM)**: CommonMark plus tables, task lists, strikethrough, autolinks, and fenced code blocks with language hints. This is what GitHub renders.
  • **MultiMarkdown**: GFM plus footnotes, definition lists, and citations. Common in academic writing.
  • **Pandoc**: a superset that supports basically every Markdown extension ever shipped. Useful for ebook and document conversion.
  • **Notion / Obsidian / Substack flavors**: each platform has its own quirks — Notion supports [[wiki-links]], Obsidian supports ==highlights==, Substack uses its own footnote syntax.

A converter that targets CommonMark will silently drop tables. A converter that targets GFM will not parse Pandoc footnotes. The output looks mostly fine until you discover that two paragraphs are missing or a table is just a row of vertical bars in the rendered HTML.

The core mapping (always works)

These are the seven Markdown features that every flavor renders the same way. If your draft uses only these, every Markdown engine on the planet renders it identically:

# Heading
## Subheading
**bold** and *italic*
[link](https://example.com)
- bullet list item
1. numbered list item
`inline code`

becomes:

<h1>Heading</h1>
<h2>Subheading</h2>
<strong>bold</strong> and <em>italic</em>
<a href="https://example.com">link</a>
<ul><li>bullet list item</li></ul>
<ol><li>numbered list item</li></ol>
<code>inline code</code>

As soon as you add tables, task lists, or footnotes, the cross-flavor compatibility breaks. The remaining sections cover what to do when your draft uses any of those.

Features that break between flavors

Five features cause about 90% of round-trip problems. Knowing which flavor your destination uses tells you which of these to avoid or rewrite.

Tables

GFM tables look like this:

| Column A | Column B |
|----------|----------|
| value 1  | value 2  |

GFM, MultiMarkdown, and Pandoc render this as an HTML <table>. Strict CommonMark renders it as a literal pipe-delimited paragraph. If your converter is CommonMark-only, your tables will appear as text noise.

Task lists

- [ ] todo and - [x] done are GFM-only. CommonMark renders them as bullet lists with literal [ ] and [x] brackets, which looks fine on a quick read but does not produce checkboxes in the rendered HTML.

Strikethrough

~~deleted text~~ is GFM. Other flavors render it as four tildes around literal text — visually obvious that something is wrong, but easy to miss until publication.

Autolinks

In GFM, a bare URL like https://example.com becomes a clickable link automatically. In strict CommonMark, you need <https://example.com> (with angle brackets) for the same behavior, or the explicit [text](url) syntax.

Footnotes

Some text[^1] with [^1]: footnote body later — supported in MultiMarkdown and Pandoc, not in GFM or CommonMark. Most cross-platform converters ignore footnote syntax silently, which is the worst failure mode because the markers stay in the output as literal text.

GFM by default
Markdown to HTML targets GFM by default — tables, task lists, strikethrough, autolinks all work. Footnotes are flavor-specific; if your draft uses them, check the output before copying. The converter flags any unprocessed Markdown directives in its preview so the regression is visible at conversion time, not at publish time.

The practical workflow

  1. Write your Markdown wherever you write — Notion, Obsidian, VSCode, your README.
  2. Paste it into Markdown to HTML.
  3. Review the rendered HTML preview side-by-side with your original Markdown.
  4. Copy the HTML output.
  5. Paste into the destination CMS, email tool, or wherever it goes.

That is the entire flow for 90% of cases. The remaining 10% is destination-specific quirks — Gmail strips classes, Substack only accepts a subset of HTML, Medium has no real Markdown import — covered below.

Pasting into Gmail and other email tools

Gmail's compose box accepts pasted HTML, but it strips most CSS classes and inline styles. So the HTML output of a Markdown converter renders correctly — headings, lists, bold — but anything you styled with classes (like class="fancy") gets dropped. The structural tags survive; the styling rarely does.

For email-friendly Markdown:

  1. Convert through Markdown to HTML.
  2. Paste into the Gmail compose box (use the rich-text mode, not plain text).
  3. Verify headings, lists, and links survived.
  4. Re-style anything that did not survive using Gmail's built-in formatting.

For richly styled emails (newsletters, transactional templates), do not paste — use a real email builder that respects MIME multipart and inlined CSS. Pure pasted HTML is for one-off emails, not designs you need to reuse across sends.

Going the other way: HTML to Markdown

Free Tool
Open the HTML to Markdown converter (free)
Convert HTML to Markdown in your browser. Free online HTML to Markdown converter that preserves headings, lists, links, and code blocks.

HTML is strictly more expressive than Markdown, so HTML → Markdown is always lossy. Classes and inline styles drop. Layout (floats, grids) collapses. Custom elements become unstyled text. Use cases:

  • Cleaning up rich-text editor output — Word, Notion, Google Docs — into something you can keep in version control.
  • Importing legacy CMS content into a Markdown-based site (Hugo, Jekyll, Astro, Next.js).
  • Summarising an email or webpage into clean prose.

For all of these, HTML to Markdown does the heavy lifting — it preserves headings, lists, links, code blocks, and tables. Anything without a Markdown equivalent is dropped or kept as inline HTML.

The "rich text paste" problem

Common scenario: you copy a paragraph from a Word doc, a Notion page, or a Google Doc. You paste into a Markdown editor, and what arrives is HTML — <span style="font-family:Arial"> and <p class="MsoNormal"> everywhere. The editor has no idea what to do with it, but the surface preview looks fine and you only notice the problem when the file gets converted again later.

Two recovery paths, depending on whether you want the formatting:

Convert to Markdown first

Paste the HTML into HTML to Markdown. The converter strips the inline styles, drops the WYSIWYG noise, and returns clean Markdown. Headings stay headings, lists stay lists, links stay links. Everything else turns into plain prose, which is usually what you wanted.

Strip to plain text

If you do not need formatting at all, run the HTML through Remove HTML Tags for just the text content. The right call when you are quoting a paragraph and rewriting it anyway.

Never paste rich text directly
When you paste from a WYSIWYG source, never paste directly into a Markdown editor. The HTML noise survives in the source even after the rendered preview looks fine, and shows up later when someone reads your file in a plain editor or when the Markdown gets converted back to HTML for publication. Always run it through HTML to Markdown or Remove HTML Tags first.

Code blocks specifically

Code blocks in Markdown use triple-backtick fences:

```js
const x = 42;
```

The js after the opening fence is the language hint, and it does two things:

  • Tells the renderer how to apply syntax highlighting (colors, italics, etc.).
  • Sets the language-js class on the output <pre><code> element, which lets your CSS or syntax library (Prism, highlight.js, Shiki) target the right language.

Skip the language hint and the renderer either picks a default (often text — no highlighting) or auto-detects (often wrong on short snippets). For the destinations that matter — GitHub, Substack, Stack Overflow, dev.to — the language hint is what makes the code look professional. Always include it. Common hints: js, ts, python, bash, sql, json, yaml. When in doubt, use the full name (javascript) — it works everywhere.

Pasting into Substack, Notion, Medium

Each of these platforms has its own Markdown import behavior, and the right tool for each is different.

Substack

Has a partial Markdown import — supports headings, lists, bold/italic, links, blockquotes, and code blocks. No tables, no task lists. If your post has a table, paste the HTML output of Markdown to HTML into Substack's editor instead — it accepts pasted HTML and renders the table correctly. The same trick works for any GFM feature that Substack's native importer drops.

Notion

Notion supports a quirky Markdown subset — backticks for inline code, asterisks for bold, but its own blocks for callouts and toggles that have no Markdown syntax. The reliable path is: convert to HTML with Markdown to HTML, then paste into Notion. Notion's HTML import is more complete than its Markdown import, and a single paste creates the right block types.

Medium

Medium has no real Markdown import. The recommended path is to write directly in their editor or use the API. For pasted content, paste the rendered HTML — Medium's editor cleans it up and applies its own styling on top. Trying to paste Markdown source into Medium gives you literal text with backticks visible.

For developers: generating HTML from Markdown safely

If you are building a static site or doc generator from Markdown files, the security consideration is HTML escaping. Most Markdown specs allow inline HTML (<script>, <iframe>, <style>) by default. If user input flows into your Markdown source — comments, user-generated docs, anything not vetted — you have an XSS vector built in.

The fix is sanitisation. Render Markdown to HTML, then run the HTML through a sanitiser (DOMPurify, sanitize-html, isomorphic-dompurify). For one-off conversions where you control the input, the Markdown to HTML tool sanitises by default — script tags, event handlers, and dangerous URL schemes are stripped on the way out.

For escaping HTML that you want to display literally — in documentation about HTML, for example, or in a code comment about API responses — use HTML Encoder. It converts < to &lt; and > to &gt; so the markup renders as text instead of executing. The reverse direction is HTML Decoder, which turns the entities back into characters when you receive escaped HTML you want to read.

Worked example: README to blog post

You wrote a README for an open-source project. Now you want to publish it as a blog post on your company site. The Markdown source has a heading, a code block, a table, and a few links:

# Quickstart

Install with npm:

```bash
npm install acme-cli
```

| Command | Effect |
|---------|--------|
| `acme up` | Start the service |
| `acme down` | Stop the service |

See the [docs](https://example.com/docs) for advanced options.

Paste through Markdown to HTML and the output is:

<h1>Quickstart</h1>
<p>Install with npm:</p>
<pre><code class="language-bash">npm install acme-cli
</code></pre>
<table>
<thead><tr><th>Command</th><th>Effect</th></tr></thead>
<tbody>
<tr><td><code>acme up</code></td><td>Start the service</td></tr>
<tr><td><code>acme down</code></td><td>Stop the service</td></tr>
</tbody>
</table>
<p>See the <a href="https://example.com/docs">docs</a> for advanced options.</p>

The table survives, the code block keeps its language hint for syntax highlighting, the inline code in the table cells is preserved, and the link is intact. Paste this HTML directly into a CMS that does not support Markdown and the page renders identically to the GitHub README.

Workflow summary

  1. Write in Markdown — Notion, Obsidian, VSCode, anywhere.
  2. Paste into Markdown to HTML.
  3. Review the HTML output. If something looks wrong, the issue is almost always a flavor mismatch (table or task list).
  4. Copy the HTML; paste into Gmail, Substack, your CMS, or wherever.
  5. Going the reverse direction (HTML to Markdown), use HTML to Markdown. Expect lossy output for anything beyond basic formatting.
  6. For raw text only (no formatting), use Remove HTML Tags on either input.

Bookmark Markdown to HTML — for anyone who writes Markdown and publishes anywhere except a code repository, this is a daily tool. The next time someone says the table broke when I pasted, the question is which flavor mismatch caused it, not whether to give up and retype the table by hand.

Frequently Asked Questions

Why does my Markdown table not render after conversion?

Tables are a GitHub Flavored Markdown (GFM) feature, not part of strict CommonMark. If the converter targets CommonMark, the table renders as literal pipe-delimited text. The [Markdown to HTML](/markdown-to-html) tool uses GFM by default, so tables, task lists, and strikethrough all work. If the destination only accepts CommonMark, paste the HTML output instead of the Markdown source.

Is the Markdown to HTML conversion safe to use on private content?

Yes. The conversion runs entirely in your browser using JavaScript loaded once with the page. Your draft never leaves your device — no upload, no logging. You can verify by opening browser devtools, switching to the Network tab, and watching while you convert. There are no outbound requests on conversion.

Does HTML to Markdown preserve all my formatting?

No, and it cannot. HTML is more expressive than Markdown — there is no Markdown equivalent for `class`, inline styles, or custom elements. The [HTML to Markdown](/html-to-markdown) tool preserves headings, lists, links, code blocks, tables, and basic emphasis. Anything beyond that is either dropped or kept as inline HTML, depending on the option you pick.

How do I keep code block syntax highlighting after pasting?

Always include the language hint after the opening triple-backtick (` ```js `, ` ```python `, ` ```bash `). The converter sets `class="language-js"` on the output, which Prism, highlight.js, Shiki, and most CMS syntax libraries pick up. Without the hint, the renderer either guesses (often wrong) or falls back to plain text with no colors.

Why does pasting from Word or Notion into a Markdown editor break things?

Because what you pasted is HTML, not Markdown. WYSIWYG sources keep formatting as inline styles and classes, and Markdown editors treat those literally. Run the paste through [HTML to Markdown](/html-to-markdown) first to clean it up, or through [Remove HTML Tags](/remove-html-tags) if you want plain text only. Never paste rich-text content directly into a Markdown source file.

Can I paste Markdown directly into Substack or Medium?

Substack supports a partial Markdown import — basic syntax works, but tables, task lists, and footnotes do not. Medium has no real Markdown import. For both, paste the HTML output of [Markdown to HTML](/markdown-to-html) instead of the Markdown source. The rendered HTML is more reliably preserved than raw Markdown across these platforms.

How do I display Markdown or HTML as literal text in my docs?

Use [HTML Encoder](/html-encoder) on the snippet you want to display. It converts `<`, `>`, `&`, and quotes to their HTML entity equivalents (`&lt;`, `&gt;`, `&amp;`), so the markup renders as text instead of executing. To reverse the process — for example, when you receive escaped HTML in a JSON response — use [HTML Decoder](/html-decoder) to turn the entities back into characters.

Tools in this guide