HTML to Markdown: A Real Migration in 6 Easy Steps
Maya had 42 blog posts trapped in an old CMS and one weekend to move them. The new static site reads only Markdown, so the job was HTML to Markdown, and it had to keep every table, link and code sample. This is a made up but typical case, and it shows what really happens.
HTML to markdown questions from the field
Is my HTML sent to a server during HTML to Markdown conversion?
No. The conversion runs in your browser, so nothing leaves your device.
Can HTML to Markdown conversion handle tables?
Yes. Turn on the convert tables option and you get pipe table syntax with a header separator row.
What is GitHub Flavored Markdown?
It is an extension of Markdown used by GitHub and many documentation tools. It adds tables, strikethrough, task lists and fenced code blocks with a language.
What happens to tags with no Markdown equivalent?
Structural tags such as div and section are flattened. Switch on strip remaining HTML to remove any other unsupported tags and keep plain Markdown.
Can I preview the Markdown before downloading?
Yes. The preview tab renders it as a quick visual check. It is simpler than a full Markdown engine, so treat it as a sanity check.
Do I choose hyphens or asterisks for bullets?
Either works for HTML to Markdown output. Both render the same in any Markdown processor, so match the style guide of the site you publish on.
The HTML to Markdown scenario: 42 posts, one weekend
The old CMS exported each post as an HTML file. Inside sat the article, wrapped in layout divs, with a navigation bar above it and a footer below. The posts used headings, bold text, lists, two or three tables, and now and then a code sample.
Maya did the sums first. Forty two posts at four minutes each is 168 minutes, a little under three hours. That left room for fixes. The risk was not time. It was a silent mistake repeated 42 times.
Step 1: pick one pilot post
She chose the most complicated post: six headings, nine links, two tables and one code block. If the converter handled that, the simple posts would be fine. Here is a cut down version of it.
Pilot post, trimmed (HTML)
<div class="post">
<h2>Our 2025 pricing</h2>
<p>We now offer <strong>three</strong> plans. See the <a href="/pricing" title="Plans">pricing page</a>.</p>
<ul><li>Starter</li><li>Team</li><li>Studio</li></ul>
<table>
<tr><th>Plan</th><th>Seats</th></tr>
<tr><td>Starter</td><td>1</td></tr>
<tr><td>Team</td><td>5</td></tr>
</table>
<pre><code class="language-bash">npm run build</code></pre>
</div>
Step 2: run the conversion and read the output
The converter does not use find and replace on the text. It hands the markup to the browser, which builds a real tree of elements, and then it walks that tree. Because the browser has already sorted out the nesting, the layout div simply disappears and its children are converted. No stray symbols are left behind.
Result (Markdown)
## Our 2025 pricing
We now offer **three** plans. See the [pricing page](/pricing "Plans").
- Starter
- Team
- Studio
| Plan | Seats |
| --- | --- |
| Starter | 1 |
| Team | 5 |
```bash
npm run build
```Every piece is where it should be. The heading became two hash marks. Bold became double asterisks. The link kept its title. The table gained a separator row of dashes, which Markdown needs to recognise a table. The code block kept its language, read from the class on the code element.
| HTML | Markdown | Note |
|---|---|---|
<h2> | ## | One to six hashes for h1 to h6 |
<strong> | **text** | Same for the b tag |
<em> | *text* | Same for the i tag |
<a href title> | [text](url "title") | Title is optional |
<ul><li> | - item | Or * item if you tick the asterisk option |
<table> | Pipe table with separator row | GitHub Flavored Markdown |
<pre><code class> | Fenced block with language | Language read from the class |
<div> | Nothing | Children are kept, wrapper removed |
Step 3: fix the four problems the pilot revealed
The pilot looked good. Then Maya checked the rest of the post and found four things. Every migration meets some version of them.
| Problem | Cause | Fix |
|---|---|---|
| A link looks broken in one place | The title attribute contained a double quote, and the converter does not escape quotes inside titles | Edit that title before converting, or fix the line by hand afterward |
| Table cells ran together as plain text | The convert tables option was off | Turn it on for any post with data |
| Menu items and footer text appeared in the output | The export included the whole page, not only the article | Paste only the article container, or delete the extra HTML first |
| Images pointed to the old server | Image source paths were copied as they were | Move the image files and update the paths in a search and replace |
The converter inserts a link title into the Markdown as it is, without escaping quote marks. A title that itself contains a double quote cuts the Markdown link short. Scan for it before a big batch, using a search for the title attribute.
If your source markup is messy or minified, run it through the HTML formatter first. Indented markup is far easier to trim down to the article container.
With table conversion off, table tags fall through to the generic handler, which strips the tags and keeps only the cell text. Turn the option on and convert again.
Containers such as div, section and header add no Markdown construct. Their children are converted and the wrapper is dropped.
Step 4: set the options once
| Option | Setting | Why |
|---|---|---|
| GitHub Flavored Markdown | On | The new site renders tables and strikethrough |
| Convert tables | On | Posts contain real data tables |
| Preserve code blocks | On | Keeps fenced blocks with their language |
| Strip remaining HTML | On | Removes tags that have no Markdown form |
| Use asterisk for bullets | Off | Hyphens match the site style guide |
GitHub Flavored Markdown matters most. Plain Markdown has no table syntax, so the table support, the strikethrough and task lists all come from that extension. The specification requires the header row and the separator row to have the same number of cells, otherwise the table is not recognised. The converter builds the separator to match the header, including padding for short rows.
Step 5: run the batch and verify
Maya converted the posts one at a time and used the counters under the output as a check: words, lines, headings and links. Her rule was simple. The number of headings and links in the Markdown must equal the number in the source.
| Post | Headings in HTML | Headings in Markdown | Links in HTML | Links in Markdown | Result |
|---|---|---|---|---|---|
| Pricing 2025 | 6 | 6 | 9 | 9 | Pass |
| Team update | 3 | 3 | 4 | 4 | Pass |
| Launch notes | 5 | 4 | 11 | 9 | Check: the last section was missing |
The third post failed on both counts. She had copied only part of the article, so the last section never reached the converter. She pasted the full container and converted again. The mismatch cost two minutes and prevented a cut off post from going live. The preview tab then rendered the Markdown so she could see headings and tables as a reader would.
- Paste the article HTML onlyCut away navigation, footer and scripts before you paste.
- Convert and open the previewLook at headings, tables and code blocks visually.
- Compare countersHeadings and links in the output must match the source.
- Download the fileSave it as an md file with the post slug as its name.
- Log the resultNote pass or fix for each post so you can resume after a break.
Step 6: handle the small details
By post ten the routine was steady, and the remaining differences were small ones. Maya kept this list next to the keyboard so she knew what to expect rather than what to fear.
| HTML | Markdown result | What to check |
|---|---|---|
<blockquote> | Each line starts with > | Multi paragraph quotes keep their prefix on every line |
<ol><li> | A numbered list rebuilt from list position | Numbers restart at 1 in each list |
<img src alt> |  | Alt text is kept, so write good alt text first |
<del> or <s> | ~~text~~ | Needs the GitHub Flavored option on the site too |
Inline <code> | Backticks around the text | Fine for short snippets |
By Sunday afternoon all 42 files sat in the new site folder, each with a log line marked pass or fixed. The site build ran without a single warning, which was the real test. Nothing about the HTML to Markdown process was clever. It was repeated, counted and checked.
What the story teaches
Markdown is a smaller language than HTML. Conversion keeps meaning and drops styling. That is the right trade for text, and it is why you must decide what you can afford to lose.
Survives conversion
- Headings, paragraphs and line structure
- Bold, italic and strikethrough
- Links with titles and images with alt text
- Ordered and unordered lists
- Tables and fenced code blocks
Does not survive
- CSS classes, ids and inline styles
- Colors and fonts
- Layout wrappers such as div and section
- Tags with no Markdown form, if stripping is on
- Anything that relies on scripts
If you need the reverse direction, for instance to preview how the Markdown will look on the web, the Markdown to HTML converter closes the loop.
Your own migration checklist
Try HTML to Markdown on your own article now. Conversion, preview and download all happen in your browser.
Open the HTML to Markdown ConverterSources: GitHub Flavored Markdown Spec
