HTML to Markdown Converter
Rendered preview
How to Convert HTML to Markdown
Paste HTML into the left panel and the Markdown appears on the right as you type. You can paste raw source (a whole page from View Source, or just a fragment), or copy a section of any web page, Google Doc or Word document and paste it straight in. With Rich paste ticked, the tool takes the formatted HTML version your browser puts on the clipboard instead of the plain text, so headings, links, bold text and lists come through.
The converter parses your HTML with the browser's own HTML parser, the same one that renders web pages, so unclosed tags and messy markup are handled the way a browser would handle them. It then walks the document and writes GitHub Flavored Markdown: CommonMark plus tables, strikethrough and task lists. Scripts, styles, iframes, buttons and form fields are dropped. Nothing is sent to a server, which matters when the page you are converting is an internal wiki, a private doc or an email.
Click Markdown → HTML to go the other way, or Use as Input ⇄ to feed the result back in and check that the conversion round-trips. The rendered preview under the panels shows the HTML side as a browser displays it. It runs in a sandboxed frame with scripts disabled.
HTML to Markdown Reference
This is what the HTML to Markdown converter writes for each element. Every row was produced by pasting the HTML into the tool above.
| HTML | Markdown output |
|---|---|
| <h2>Install</h2> | ## Install |
| <strong>, <b> | **bold** |
| <em>, <i> | *italic* |
| <strong><em>both</em></strong> | **_both_** |
| <del>, <s>, <strike> | ~~old~~ |
| <code>a `b` c</code> | ``a `b` c`` |
| <a href="…" title="…"> | [text](url "title") |
| <a href="https://example.com">https://example.com</a> | <https://example.com> |
| <img src="/logo.png" alt="Logo"> |  |
| <br> | A backslash at the end of the line |
| <blockquote> | > before every line, nested quotes become > > |
| <hr> | --- |
| <ol start="3"> | 3. three, 4. four |
| <input type="checkbox" checked> in <li> | - [x] Done |
| <sup>, <sub>, <kbd>, <u>, <mark> | Kept as inline HTML, e.g. mc<sup>2</sup> |
| <script>, <style>, <iframe>, <button> | Removed |
| <div>, <span>, <section> | Unwrapped; only their content is kept |
Tables, Nested Lists and Code Blocks
These three are where most HTML to Markdown converters fail. Tables get flattened into lines of text, nested lists lose their indentation, and code blocks lose their language or break when the code itself contains backticks. This tool keeps all of them.
Tables become GFM pipe tables. Column alignment is read from the align attribute or from an inline text-align style, so a right-aligned price column stays right-aligned:
<table>
<tr><th>Plan</th><th align="right">Price</th><th align="center">Users</th></tr>
<tr><td>Free</td><td align="right">$0</td><td align="center">1</td></tr>
<tr><td>Team</td><td align="right">$12</td><td align="center">10</td></tr>
</table>
| Plan | Price | Users |
| --- | ---: | :---: |
| Free | $0 | 1 |
| Team | $12 | 10 |
A pipe character inside a cell is escaped as \|, including inside inline code, so <code>a | b</code> in a cell becomes `a \| b` and still renders as a | b. A <br> inside a cell is written as a literal <br>, because a Markdown table row has to stay on one line.
Nested lists are indented by the width of the parent's marker, which is what CommonMark requires, so a numbered list inside a bullet keeps its nesting when it is rendered again:
<ul>
<li>Backend
<ol>
<li>Install Node</li>
<li>Run <code>npm ci</code></li>
</ol>
</li>
<li>Frontend</li>
</ul>
- Backend
1. Install Node
2. Run `npm ci`
- Frontend
Code blocks become fenced blocks with a language hint. The language is taken from a language-* or lang-* class (the convention used by Prism, highlight.js and most Markdown renderers), from GitHub's highlight-source-* wrapper, or from a data-lang attribute. So <pre><code class="language-python"> opens with ```python. Whitespace inside <pre> is kept exactly, including indentation and blank lines. If the code itself contains three backticks, the fence is made longer, so Use ```js to open a fence is wrapped in four backticks and does not end the block early.
Markdown to HTML
Switch the direction and the tool becomes a Markdown to HTML converter. It implements CommonMark, including its rules for emphasis, link reference definitions, setext headings and lazy continuation lines, plus the GitHub extensions for tables, ~~strikethrough~~ and task lists. We converted thousands of randomly generated documents with both this tool and markdown-it, the parser behind many documentation sites, and the output matched. The only differences were a few contrived cases of unmatched * or _ runs inside link text, such as [*w=**](url); there markdown-it departs from commonmark.js, the CommonMark reference implementation, and this tool gives the reference result.
A few behaviours worth knowing:
- Tight and loose lists.
- oneand- twoon consecutive lines give<li>one</li>. Put a blank line between the items and the list becomes "loose": every item is wrapped in<p>, which adds vertical space when it is rendered. - Line breaks. A single newline inside a paragraph is a soft break and renders as a space. For a
<br>, end the line with two spaces or a backslash. - Task lists.
- [x] Donebecomes<input type="checkbox" disabled checked> Done. - Raw HTML passes through unchanged, so a
<details>block still works, and Markdown inside it is converted when it is separated from the tags by blank lines. - Unsafe links. A link to
javascript:,vbscript:orfile:is written without itshref, so[click](javascript:alert(1))becomes<a>click</a>. - Bare URLs are not turned into links. Wrap them in angle brackets, as in
<https://example.com>.
Converting Copied Web Pages and Google Docs
When you copy formatted text, the clipboard holds two versions: plain text and HTML. A normal paste into a text box uses the plain text. With Rich paste on, this tool reads the HTML version, so you can select an article in your browser, copy it and paste it here to get Markdown with the headings, links and lists intact. Untick the option to paste plain text instead, for example when you are pasting HTML source code that you copied from an editor.
Google Docs does not use <strong> and <em>. It marks bold, italic and strikethrough with inline styles such as <span style="font-weight:700">, and it wraps the whole paste in <b style="font-weight:normal">. The converter reads those styles, so pasted Docs text comes out as **Bold** and *italic* text rather than either losing its formatting or turning bold everywhere.
What Markdown Cannot Express
Markdown is smaller than HTML, and some things have no equivalent. This is what the tool does with them:
- Classes, IDs and inline styles are dropped. Markdown has no syntax for them.
- Merged cells. A cell with
colspan="2"is followed by an empty cell, so the columns still line up. Pipe tables cannot merge cells. - Superscript, subscript, keyboard keys, underline and highlight are kept as inline HTML, which Markdown allows:
E = mc<sup>2</sup>, press <kbd>Ctrl</kbd>+<kbd>C</kbd>. Untick the option to keep just their text. - Block content inside a table cell, such as paragraphs or lists, is joined with
<br>, since a pipe table row is a single line. - Emphasis that the Markdown rules cannot express.
<em>(optional)</em>valuecannot be written as*(optional)*value, because CommonMark does not let a*after a bracket close emphasis when a letter follows it. The tool detects this and writes<em>(optional)</em>valueinstead of producing Markdown that renders wrongly.
Common Mistakes When Converting HTML to Markdown
- Not escaping text that looks like Markdown. A paragraph that starts with
1. Not a listor# Not a headingturns into a list or a heading if it is copied as is. The tool escapes these as1\. Not a listand\# Not a heading. In the middle of a line they are left alone, because they cannot start a block there. - Over-escaping underscores.
snake_caseis left as it is, because an underscore between two letters can never start emphasis. A leading underscore, as in_private, is escaped as\_private. - Two lists in a row. In Markdown, two bullet lists separated only by a blank line merge into one. When the HTML has two separate
<ul>elements, the tool switches the second list's bullet from-to*so it stays a separate list. - Losing entities. Text that shows a literal
©on the page must not turn into a © symbol. It is written as\©, while a real © character is kept as ©. - Converting the whole page. Pasting a full page source brings in navigation, footers and cookie banners as text. Copy just the article, or delete the extra parts from the output.
- Relative links.
href="/docs/"stays relative. That is correct when the Markdown stays on the same site, but the links break if you move the file elsewhere. Make them absolute before converting.
How to Convert HTML to Markdown in Code
For a one-off conversion, paste into the tool. To convert many files, use a library. In JavaScript, Turndown is the standard choice. Tables and strikethrough need its GFM plugin:
import TurndownService from 'turndown';
import { gfm } from 'turndown-plugin-gfm';
const td = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' });
td.use(gfm);
const markdown = td.turndown('<h2>Install</h2><p>Run <code>npm ci</code></p>');
In Python, use markdownify:
from markdownify import markdownify as md
markdown = md(html, heading_style="ATX")
From the command line, Pandoc converts files in both directions:
pandoc -f html -t gfm page.html -o page.md
pandoc -f gfm -t html page.md -o page.html
For Markdown to HTML in JavaScript, markdown-it and marked are the usual libraries. Whatever you use, render Markdown from untrusted users through an HTML sanitiser, because Markdown allows raw HTML. Many Markdown files also start with a YAML front matter block between --- lines; the YAML to JSON converter will show you what that metadata parses to.
Frequently Asked Questions
align attribute or text-align style. Pipes inside cells are escaped as \|, and line breaks inside a cell are written as <br>.<pre> blocks become fenced code blocks with the language taken from a class such as language-python. Indentation and blank lines are kept. If the code contains backticks, the fence is made longer so the block does not end early.**bold**, *italic* and ~~strikethrough~~.*, such as <em>(optional)</em>value, is also kept as HTML so it renders correctly.