HTML to markdown
The web is HTML, but your notes, READMEs, and LLM prompts want markdown. When you
copy a rich-text block, scrape a page, or get HTML back from a model, you need it as
clean markdown — real ## headings and - bullets, not a wall of <div> and
<span>. This converter parses the HTML in your browser and emits tidy markdown.
How it works
The tool feeds your HTML into the browser’s native DOMParser, producing a document
tree, then recursively walks that tree. Each element maps to its markdown equivalent —
<h2> becomes ##, <strong> becomes **, <li> becomes -, <table> becomes a
pipe table — while <script> and <style> are dropped and unknown tags are unwrapped
so their text survives.
<h2>Title</h2> -> ## Title
<a href="x">link</a> -> [link](x)
<ul><li>one</li></ul> -> - one
When to use it
The converter is most useful in a handful of recurring situations:
- Scraping and re-publishing. You fetched page HTML with
fetch()orcurl, and you want the readable prose in markdown for further processing or storage. - LLM prompt preparation. A model returned HTML output and you need to feed it back into another prompt or store it as plain text.
- README or doc writing. A colleague shared a Google Doc link; you copied the rendered text, which came in as HTML, and your repo wants a markdown file.
- Rich-text editor export. Content Management Systems often let you copy HTML source; this drops it straight to markdown in one step.
How it handles common edge cases
Tables: <table> elements become GitHub-flavoured pipe tables — | col | col | with a separator row. Column alignment attributes on <th> cells are honoured where present.
Nested lists: A <ul> inside an <li> becomes an indented markdown list. Sub-items are indented by two spaces per level, which is compatible with most markdown parsers.
Code blocks: <pre><code> pairs become fenced code blocks (triple backtick). Inline <code> becomes backtick-wrapped inline code. Language hints in a class="language-js" attribute are carried through to the opening fence.
Images: <img src="..." alt="..."> becomes . If alt is empty, the brackets are left empty, giving ![]() — valid markdown, though you may want to fill in the alt text yourself.
Links: <a href="...">text</a> becomes [text](href). Relative links survive as-is since the converter has no base URL to resolve them against.
Unknown tags: Any element the converter does not specifically map (such as a <section> or custom web component) is unwrapped, and only its text content is passed through. You lose the tag but keep the words.
Worked example
Paste this HTML snippet:
<h2>Shopping list</h2>
<ul>
<li>Bread</li>
<li>Milk
<ul>
<li>Whole</li>
<li>Skimmed</li>
</ul>
</li>
</ul>
<p>See <a href="/store">the store</a> for details.</p>
The converter outputs:
## Shopping list
- Bread
- Milk
- Whole
- Skimmed
See [the store](/store) for details.
Tips and notes
- Word and Google Docs pastes add heavy inline-style noise —
style="font-size:11pt; font-family:'Arial'"on every span. The walker ignores all styling attributes, so the markdown comes out clean regardless. - Scripts and styles are stripped, not run. The browser’s
DOMParseris used to read the HTML, so<script>blocks are parsed into a safe inert tree and never executed. - Round-tripping. Pair with the Markdown to HTML converter to go in the other direction. Common elements survive a round-trip intact; exotic CSS styling that markdown cannot represent will be lost, which is usually what you want.
- Table extraction. If you just need the data from an HTML table, the related Markdown Table Extractor tool works well alongside this one once you have the markdown form.