How to use
- Paste HTML into the left box: page source, a CMS export, an email template, the output of a rich-text editor. A fragment is fine; it does not need
<html>or<body>. - The Markdown appears on the right as you type. The line under it lists anything that was approximated (a table with no header row, an image with no address).
- Pick a Bullet character if your style guide wants
*rather than-. - Copy Markdown (or Ctrl+Enter in the input box) or Download .md.
What this converter does with real-world HTML
HTML in the wild is rarely the clean semantic kind. Rich-text editors emit <span style="font-weight:700"> instead of <strong>, CMS exports leave unclosed tags, page source carries scripts, styles and comments, and whitespace is scattered across lines the browser would collapse. This converter parses HTML the tolerant way a browser does, reads inline styles as well as tag names, drops what carries no content, and writes GitHub-flavored Markdown that keeps the structure: headings, lists with their nesting, tables, code with its language, links, images and quotes.
The output is meant to be read back as written, so characters that Markdown would interpret are escaped only where they would be interpreted. It is a converter, not a page cleaner: navigation and footers in a full page are converted along with the article. For text copied from Google Docs, Word or a web page (where the formatting lives on the clipboard, not in source you can see), Google Docs to Markdown runs the same converter on a formatted paste.
Example
Load sample inserts release notes as semantic HTML: 3 headings, a paragraph with <strong>, <em>, <code> and a link, a nested list, a numbered list, a quote, a fenced code block with a language class, a table and an image. It becomes:
## Release notes: v2.4.0
This release adds **scheduled exports** and fixes the *timezone* bug in `report.py`. Full details on the [changelog](https://example.com/changelog).
### Added
- Scheduled exports
- Daily, weekly or monthly
- CSV and JSON formats
- Dark mode for the dashboard
### Upgrade
1. Back up the database
2. Run `pip install -U reporter`
> Exports created before 2.4 keep their old schedule.
```bash
reporter export --format csv --schedule weekly
```
| Setting | Default |
| --- | --- |
| `format` | csv |
| `schedule` | none |
Options explained
- Bullet character chooses
-(default) or*for unordered list items. Nested items are indented by two spaces under a bullet and by the width of the number under a numbered item. - Load sample inserts the HTML described above; Clear empties both boxes.
- Copy Markdown and Download .md take the right-hand box as it is; Ctrl+Enter in the input box copies.
- The status line reports approximations: a table with no
<th>row (the first row is promoted), an image with nosrc(dropped), underline (kept as plain text), a Google Docs redirect link (unwrapped), ajavascript:link (text kept, address dropped).
Edge cases and common errors
- Unclosed and stray tags.
<p>one<p>two<li>stray</li>three</b>becomesone ⏎ two ⏎ stray ⏎ three: the second<p>closes the first, the orphan<li>is a paragraph, the closing</b>with no opener is ignored. - Scripts, styles and comments vanish with their contents: a
<style>, a<script>and a comment before<p>kept</p>givekept. - Whitespace collapses as in a browser, except inside
<pre>:lots of space⏎⏎```⏎ keep⏎ this⏎```. - Inline styles count.
font-weight:700,font-style:italicand a monospacefont-familyon a span give**bold** *italic* `code`, the same result as<strong>,<em>and<code>. A weight of 600 or more is bold; 500 is not. - Escaping.
2 * 3, a_b, _x_, a[b], `t`, <tag>gives2 \* 3, a_b, \_x\_, a\[b\], \`t\`, \<tag>. After a<br>, a line that would start a list, heading or quote is escaped too:\- one ⏎ 2\. two ⏎ \# three. - Tables without a header row. Markdown tables need one, so the first row is promoted and the status line says so:
| a | b | ⏎ | --- | --- | ⏎ | 1 | x \| y |. A pipe inside a cell is escaped. - Code blocks keep their language from
class="language-python"(```python⏎print("hi")⏎```) and use a longer fence when the code itself contains three backticks:````⏎echo ``` done⏎````. - Unsafe links:
<a href="javascript:alert(1)">click</a>givesclick, the text without the address, and the status line says “A link with a javascript: or data: address was dropped; its text was kept.”. - An image with no
srcis dropped and noted: “An image had no address and was dropped.”. - Entities are decoded, named and numeric:
& < © 😀gives& < © 😀. An entity the converter does not know is left as typed. - No tags at all is treated as one paragraph of text with escaping applied, and the status line says so. Empty input gives empty output.
- Size: 300 KB of HTML (thousands of headings, lists and tables) converts in well under a second; a run of thousands of stray
<characters does not slow the parser.
Do the same thing in code
Shell (pandoc)
pandoc -f html -t gfm --wrap=none page.html -o page.md
curl -s https://example.com/post | pandoc -f html -t gfm --wrap=noneJavaScript (Node.js)
import TurndownService from "turndown";
import { gfm } from "turndown-plugin-gfm";
const td = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
td.use(gfm); // tables, strikethrough, task lists
const markdown = td.turndown(html);Python
from markdownify import markdownify as md
markdown = md(html, heading_style="ATX", bullets="-")Those libraries read tag names; none of them treats font-weight:700 as bold without a custom rule, which is the difference that matters for editor and Docs output. To go the other way, from Markdown to a formatted document, use Markdown to Google Docs; to remove the formatting entirely, Markdown to Plain Text.