HTML to Markdown Converter_

Paste markup and read the Markdown back, with a note for each thing that could not make the trip. Classes, inline styles, spans and merged table cells have no Markdown spelling at all — so they are named rather than quietly deleted, which is the part every other converter leaves out.

Markdown goes to HTML exactly. HTML does not come back. That asymmetry is the whole subject: a document round-tripped through Markdown is a document with its presentation removed, and knowing precisely what was removed is usually more useful than the conversion itself.

toolkit.codes/html-to-markdown
HTML
Markdown
Removed
Smaller by
UTF-8
Ready
100% LOCAL
Input
Any HTML — a whole page, a fragment from an editor, a block copied out of a CMS. It is sanitised before anything else happens, because arbitrary markup is exactly what this tool is for.
Output
CommonMark, with GitHub pipe tables. Bullet character and fenced or indented code blocks are yours to choose; teams disagree and both are correct.
Processing
Parsed into a real DOM in this tab and walked node by node, which is the only way to know what markup means. Nothing is uploaded.
Limits
A table with colspan or rowspan, and a table inside a table, are left as HTML rather than flattened — Markdown has no way to express either, and a silent flattening loses data the reader cannot see is missing.
Why people convert to Markdown now
To make a page fit a model. On a realistic CMS fragment the markup accounts for about two thirds of the bytes: 922 down to 300, or 67.5% removed, and the content is identical. Measured on this page rather than estimated. Every class, wrapper div and inline style is cost with no meaning attached, which is why scraped HTML is fed to language models as Markdown.

One direction is exact, the other is a summary

What HTML can say that Markdown cannot

Markdown covers headings, emphasis, links, images, lists, quotes, code and — in the GitHub dialect — plain tables. That is the whole vocabulary. Anything else in your markup has no equivalent: a class, an id, an inline style, a <span>, a data attribute, a wrapper <div>, a column that spans two cells, a table nested inside another. A converter cannot preserve them, so the only real question is whether it tells you they are gone.

Merged cells are where a conversion becomes a lie

A Markdown table is a rectangle: every row has the same number of cells and no cell spans two. Hand a converter a table with colspan="2" and it must either drop the attribute — producing a table that looks fine and says something different from the original — or keep the HTML. Dropping it is the common choice, and it is the failure nobody notices, because the result is well-formed and plausible. This page keeps such tables as HTML and says why, which is uglier and honest.

Sanitising is not optional here either

The input to this tool is, by definition, markup somebody else wrote — copied from a page, pulled from an API, scraped. It has to be parsed into a real DOM to be walked, and a real DOM is exactly where a <script> would like to be. So every input goes through the same allowlist the Markdown renderer uses before a single node is read, and what was removed is counted in the notes rather than passed along.

Escaping, and the text that accidentally becomes syntax

A paragraph reading 2 * 3 * 4 is prose in HTML and italics in Markdown. The same applies to a line starting with a hyphen, a number and a dot, a hash, or a greater-than sign — all of them are plain text on one side and structure on the other. Those characters are escaped on the way out, which is why the Markdown sometimes carries backslashes that were not in your HTML. Removing them would change what the document says.

In Python, in Node, and at the command line

turndown is the usual JavaScript answer and the one most web tools are built on; markdownify and html2text are the common Python ones, and pandoc -f html -t markdown handles a file or a pipe with no install beyond pandoc itself. Reach for a library when the conversion is part of a pipeline and for a page like this when it is one document and you want to see what the conversion cost before you commit to it.

Paste it, read the notes, take the Markdown

  1. 01Paste the HTML, or use Upload for a local file. It converts on every keystroke.
  2. 02Read the notes under the size row. They name what had no Markdown equivalent, with a count — not a warning that something might have happened.
  3. 03Choose your bullet character and code-block style if your team has a convention. Neither changes meaning.
  4. 04Copy the Markdown. The size row tells you how much of the original was markup rather than content.

A page that has to fit in a model's context

Scraped HTML is mostly attributes. Markdown keeps the meaning and drops roughly two thirds of the bytes, which is why retrieval pipelines convert before they embed.

From the CMS
<p class="mb-4 leading-relaxed text-gray-800">The <strong class="font-semibold">first step</strong> is <a href="/docs" class="text-blue-600 underline">the docs</a>.</p>
What the model reads
The **first step** is [the docs](/docs).

Moving a post into a static site

The article lives in a WYSIWYG editor and the new site takes Markdown files. The prose survives; the editor's wrapper markup does not, which is the point.

Editor output
<div><h2 id="s1">Setup</h2><ul><li>Install it</li><li>Run <code>init</code></li></ul></div>
Front-matter body
## Setup

- Install it
- Run `init`

A table your documentation cannot take

The source table merges two cells in its header. Markdown has no colspan, so the choice is between a table that quietly says something else and the original markup.

With a merged header cell
<table><tr><td colspan="2">2026 totals</td></tr><tr><td>Q1</td><td>Q2</td></tr></table>
Kept as HTML, and reported
<table>…</table>

Note: a table used colspan or rowspan.

An email or a changelog pasted from a browser

Copying rendered content brings the styling with it. The conversion keeps the structure and the notes tell you how many styled spans went away.

Pasted
<p>Release <span style="color:#c00">2.4.0</span> is out.</p>
Converted, with a note
Release 2.4.0 is out.

Note: inline styling dropped (1).

What each construct becomes

HTMLMarkdownNote
<h1> to <h6># to ######Setext underlining is available for the first two levels only, because it cannot express a third.
<strong>, <b>**text**Both map to the same thing; Markdown has no separate bold and strong.
<a href>[text](url)A title attribute is kept. A link with no href becomes plain text and is counted.
<pre><code>Fenced blockA language-* class becomes the fence info string.
<table>Pipe tableOnly when the grid is plain. Merged or nested cells stay as HTML.
<span>, <div>NothingThe contents are kept and the element disappears — there is nothing to convert it to.
style, class, idNothingCounted in the notes rather than silently discarded.
<script>, <iframe>RemovedStripped by the sanitiser before conversion, and reported.

The reference table on the Markdown to HTML page is the same journey read the other way. Between them they describe a round trip and where it stops being lossless.

Habits worth having around a lossy conversion

  • Read the notes before you keep the output. They are the difference between a conversion you can trust and one that merely looks right.
  • Convert the smallest fragment that matters. A whole page brings navigation, footers and cookie banners with it, all of which survive as text.
  • Keep the HTML if the document has to go back. Markdown to HTML is exact in that direction, but the classes and ids are not coming back with it.
  • Check tables first. They are the construct most likely to be silently simplified, and the easiest to misread once it has been.
  • If the target is a language model, measure rather than assume. The saving depends entirely on how much markup the source carried — a hand-written page loses far less than a framework-generated one.

Where the conversion loses something

Merged table cells cannot survive

Markdown tables are a plain grid. Dropping a colspan produces a table that parses cleanly and means something different, which is why they are kept as HTML here instead.

Everything presentational is gone

Colours, alignment, widths, fonts, classes and ids have no Markdown spelling. The text survives and the appearance does not, and no converter can do better than say so.

Whitespace inside <code>&lt;pre&gt;</code> is content, everywhere else it is not

Ordinary HTML collapses runs of whitespace and Markdown follows suit. Inside a code block every space is preserved, which is why a document can look reflowed everywhere except its code.

Backslashes will appear that you did not write

Text containing asterisks, brackets, hashes or leading hyphens is escaped, because those characters are structure in Markdown. Stripping the escapes would change the meaning of the document.

Dialect, sanitising, and the size figures

Output dialect
CommonMark, plus GitHub pipe tables and strikethrough. No HTML is emitted except where a table cannot be expressed any other way.
Method
The markup is parsed into a DOM and walked node by node. Element semantics come from the parser rather than from pattern-matching the source text, so malformed markup converts the way a browser would read it.
Sanitiser
DOMPurify with the HTML profile, plus style attributes removed. Scripts, every on* handler, javascript: and data:text/html URLs, and the framing elements (iframe, object, embed, base, meta) do not survive. details, img, kbd, sup, table alignment and the language-* class marked puts on code blocks all do.
Sizes
UTF-8 bytes on both sides. Token counts are not shown, because tokenisation differs per model and a byte figure is the honest common denominator.
Limits
Input is refused above 2 MB. Tables with colspan, rowspan or nesting are emitted as HTML and counted.
File handling
Uploads are read inside the page with the browser File API and are never transmitted; Download writes out what is already in the tab.
Network
None from tool code. A test sweep calls every function this page uses with fetch and XMLHttpRequest replaced by stubs that throw, so a stray request fails the build instead of shipping. Disconnect from the network and the page still works.

Questions about converting HTML into Markdown

Is converting HTML to Markdown lossless?

No, and it cannot be. Markdown has no vocabulary for classes, ids, inline styles, spans, data attributes or merged table cells, so anything expressed with them has nowhere to go. The content survives; the presentation does not. Markdown to HTML is exact in the other direction, which is what makes the asymmetry easy to forget.

What happens to a table with merged cells?

It is kept as HTML and a note says why. A Markdown table is a plain rectangle, so a colspan can only be preserved by dropping it — which produces a table that parses correctly and no longer means what the original meant. That is a worse outcome than some HTML in your Markdown.

Why does the output contain backslashes?

Because the text contained characters that are structure in Markdown. A paragraph reading 2 * 3 * 4 would render as italics without escaping, and a line starting with a hyphen would become a list item. The backslashes keep the document saying what it said.

How much smaller is Markdown than HTML?

On a realistic CMS fragment, about two thirds smaller — 922 bytes down to 300, measured on this page. The saving depends entirely on how much markup the source carried: framework-generated HTML full of utility classes loses far more than a hand-written page does.

Why do people convert HTML to Markdown for AI?

Because the markup is cost with no meaning attached. A model reading a page does not benefit from wrapper divs and class attributes, and they consume context that could hold content instead. Markdown keeps the structure a model uses — headings, lists, emphasis, links — and discards the rest.

How do I convert HTML to Markdown in Python?

markdownify and html2text are the two common libraries, and pandoc -f html -t markdown handles it from the command line without writing any code. Use a library when it is part of a pipeline; a page like this is for the single document where you want to see what the conversion cost first.

Are scripts and iframes removed?

Yes, before anything is converted. The input is arbitrary markup and it has to be parsed into a real DOM to be read, so the same allowlist the Markdown renderer uses runs first. What was removed is counted in the notes rather than passed through.

Can I get the HTML back afterwards?

You can render the Markdown to HTML and the structure will return, but the classes, ids and styles will not — they were never stored. If the document has to go back in its original form, keep the original.

Does my HTML leave the browser?

No. The work is JavaScript running in this tab. Every function it calls is covered by a test that stubs fetch and XMLHttpRequest to throw, so a request that slipped in would break the build rather than reach a server — and you can confirm it for yourself by disconnecting and carrying on.