100% Local · Free · No Sign-up

Convert HTML to Clean Markdown (GFM)

Convert an .html file or a pasted fragment into clean, GitHub-flavoured Markdown. DOMPurify sanitises the markup first, then Turndown maps semantic HTML5 — article, table, pre, code, blockquote — to CommonMark and GFM, entirely inside your browser.
Convert HTML to Clean Markdown (GFM)Drag & drop · .html, .htm

Processing happens 100% in your browser — nothing is uploaded.

Presentation is discarded, and spans cannot be expressed

Inline styles, stylesheets, and class-based layout have no Markdown equivalent and are stripped. Cells spanning rows or columns are emitted as ordinary cells, since GFM pipe tables cannot express spans — merged-header tables need manual repair, and nested tables flatten.
100% Local Processing — Your files never leave your device

How to convert HTML to Markdown

  1. Input: drop an .html or .htm file onto the dropzone, or paste raw markup — a full document or a single fragment both work.
  2. Process: DOMPurify sanitises the markup, then Turndown maps it to GitHub-flavoured Markdown locally in your browser.
  3. Copy: send the Markdown to your clipboard or save it as a .md file.

Why Convert HTML to Markdown with MD Convert?

  • Sanitised before it is converted, never after

    DOMPurify runs first, so scripts, event handlers, and javascript-scheme URLs are gone before Turndown or the preview ever sees the markup. Style and form elements plus every style attribute are explicitly forbidden — you can convert customer-supplied or scraped HTML without executing any of it.

  • GFM tables with pipe escaping and header synthesis

    The first row is promoted to a header and a delimiter row is generated from its cell count, so tables that omit a thead still produce valid GFM. Literal pipes inside cells are escaped and cell newlines collapse, so one value can never split its row and shift your data.

  • Semantic structure in, portable Markdown out

    ATX headings, fenced code blocks with the language lifted from the code element's class, hyphen bullets, nesting blockquotes, inlined links, and hard breaks as two trailing spaces. The output needs no reformatting to travel between GitHub, GitLab, Obsidian, Hugo, and Jekyll.

  • 100% local — your markup never leaves the device

    Both libraries are ordinary browser JavaScript. Nothing is uploaded, nothing is retained, and conversion keeps working with the network disconnected — which is what makes it usable for internal templates and proprietary markup.

HTML to Markdown: Before and After

Semantic HTML5 in, GitHub-Flavored Markdown out. Note the presentational span and the inline style attribute: both are discarded, because Markdown has nowhere to put them.

Input · html
<article>
  <h2>Release notes</h2>
  <p style="color:#c00">Shipped <strong>v2.1</strong> with
    <em>streaming</em> parsers.</p>
  <blockquote><p>Peak memory is now flat.</p></blockquote>
  <table>
    <tr><th>Engine</th><th>Status</th></tr>
    <tr><td>pdf</td><td>stable</td></tr>
    <tr><td>xml</td><td>beta | preview</td></tr>
  </table>
  <pre><code class="language-ts">const md = convert(file);</code></pre>
  <span class="badge">decorative</span>
</article>
Output · Markdown
## Release notes

Shipped **v2.1** with _streaming_ parsers.

> Peak memory is now flat.

| Engine | Status |
| --- | --- |
| pdf | stable |
| xml | beta \| preview |

```ts
const md = convert(file);
```

decorative

Understanding the HTML Format: WHATWG HTML Living Standard

Format
HyperText Markup Language
Media type
text/html

HTML and Markdown are not rival syntaxes for the same thing; they describe overlapping but unequal sets. HTML can express a form, an inline style, a data attribute, a table cell spanning three columns and a nested table inside that cell. Markdown expresses roughly a dozen block constructs and half a dozen inline ones. Converting in this direction is therefore a projection: everything with a Markdown counterpart maps cleanly, and everything without one has to be dropped, flattened, or passed through as literal HTML. Knowing which category a given tag falls into is the difference between output you can commit and output you have to hand-fix.

The pipeline runs in a fixed order, and the order is a security property rather than a stylistic preference. The incoming markup is first sanitised with DOMPurify, then handed to Turndown. Sanitising first means no untrusted markup ever reaches the live DOM used for preview, and no script, event handler or javascript-scheme URL survives long enough to be executed or to be smuggled into the Markdown as an inline link. Converting first and sanitising the Markdown afterwards would be the wrong order, because Markdown permits raw HTML — an unsanitised payload would simply pass through as text and detonate wherever the Markdown was later rendered.

Turndown itself is configured for portability rather than compactness: ATX-style headings with hash marks, fenced code blocks with backticks, hyphen bullets for unordered lists, underscore emphasis, double-asterisk strong, inlined links, and horizontal rules as three hyphens. Those are the choices with the widest support across GitHub, GitLab, Obsidian, Notion imports, Hugo, Jekyll and every editor that reads CommonMark, so the output does not need reformatting to travel between them.

Sanitisation: what DOMPurify removes before conversion

DOMPurify parses the input into an inert document, walks every node, and deletes anything outside an allow-list before the markup is serialised back. This build runs it with the standard HTML profile plus a narrow set of additions, and the important part is what is explicitly forbidden: style, form, input and button elements are removed outright, and the style attribute is stripped from every element that carries one.

Those exclusions are deliberate and each has a reason. A stylesheet or style attribute has no Markdown representation at all, so keeping it would only leak presentational noise into the output or, worse, into a preview where it could reposition the surrounding interface. Form controls are interactive elements whose behaviour cannot survive conversion, and a stray submit button rendered inside a document preview is a genuine clickjacking surface rather than a cosmetic wart. Script and event-handler attributes are removed by the sanitiser's own defaults, and the permitted URL schemes are constrained to a small set, which is what neutralises a link whose target is a javascript-scheme payload.

Turndown is additionally told to discard script, style, noscript and frame elements outright, so even markup that survives sanitisation as an empty shell contributes nothing to the output. The practical consequence: you can paste markup from an email, a scraped page or a customer-supplied template into the editor and read the result without having executed any of it.

Tables: the GFM rules this converter actually uses

Pipe tables are not part of CommonMark; they are a GitHub-Flavored Markdown extension, and getting them right from arbitrary HTML takes more than mapping cells to pipes. This build implements its table handling as explicit rules rather than relying on a plugin, and three of those rules exist because of specific failures observed in real documents.

First, the first row of a table is always treated as the header row, whether its cells are header cells or ordinary ones. Real-world markup — particularly markup generated from Word documents — frequently omits a table head entirely, and a pipe table without a delimiter row beneath its first line is not a valid GFM table. Emitting one used to hang the preview renderer, so the first row is promoted unconditionally and a delimiter row is synthesised from its cell count.

Second, literal pipe characters inside cell values are escaped, and newlines inside a cell are collapsed to spaces. Without both, a single cell containing a pipe silently splits its row into extra columns and misaligns every value after it — the kind of corruption that looks like valid output and quietly changes your data. Third, table section elements are passed through untouched, because their default block-level treatment inserted a blank line between the header and the body, which terminates a table in GFM and leaves the remaining rows orphaned as a second, headerless table.

What is not attempted is spanning. Cells that span rows or columns have no GFM equivalent whatsoever; they are emitted as ordinary cells, so a table built on merged headers will need manual repair. Nested tables flatten for the same reason.

Element-by-element mapping, and the gaps

Heading levels one through six become the corresponding number of hash marks. Paragraphs become blank-line-separated blocks. Strong and bold become double asterisks; emphasis and italic become underscores. Anchors become inline links with their href preserved; images become image syntax with their alt text and source. Unordered and ordered lists become hyphen and numbered items, nesting by indentation to arbitrary depth. Blockquotes become angle-bracket prefixes and nest correctly. Horizontal rules become three hyphens. Line-break elements become the two-trailing-space hard break, which is the only way CommonMark expresses a break inside a paragraph.

Code is the case worth understanding. Inline code elements become backtick spans. A code element inside a preformatted element becomes a fenced block, and the language label is lifted from the code element's class name when it follows the conventional language prefix — so markup produced by a syntax highlighter keeps its language tag, which matters because that tag is what drives highlighting wherever the Markdown lands. A preformatted block with no code element inside it is still fenced, but without a language hint.

The gaps are honest and short. Description lists have no Markdown form and flatten to plain lines. Superscript, subscript, keyboard and abbreviation elements lose their semantics, retaining only their text. Details and summary disclosure widgets flatten. Definition semantics, ruby annotations and figure captions become ordinary text adjacent to the content they described. Anything genuinely unmappable is passed through as raw HTML rather than deleted, which keeps the document lossless at the cost of leaving some tags visible in the output — a trade in favour of never silently discarding content.

Local execution, whitespace and document fragments

Both libraries are ordinary browser JavaScript, so the entire conversion happens inside your tab. There is no request to any endpoint, no temporary file on a server, and nothing to retain, which is what makes the tool usable for internal templates, proprietary email markup and documents under a confidentiality obligation. Working offline is a corollary rather than a feature: once the page is loaded, conversion continues to function with the network disconnected.

Input does not need to be a complete document. A fragment — a single article element, a table pulled out of DevTools, a snippet copied from an editor — converts exactly as well as a full page, because the sanitiser builds a document around whatever it is given. A complete document works too; head content and metadata carry no convertible text and simply produce nothing.

Whitespace handling follows the HTML rules rather than the source formatting, which is usually what surprises people first. Runs of spaces and newlines inside a block element collapse to a single space, because that is what HTML says they mean, so hand-indented markup does not leak its indentation into the Markdown. Preformatted content is the exception and is preserved byte-for-byte. If your output has lost line breaks you expected to keep, they were almost certainly source formatting rather than break elements, and the fix is to mark them up as breaks or paragraphs.

Known limitations of HTML to Markdown conversion

Being explicit about what a converter cannot do saves you a wasted upload. These are the boundaries of what is recoverable from HTML programmatically:

  • Inline styles, stylesheets and class-based presentation are discarded — Markdown has no styling layer to hold them.
  • Cells spanning multiple rows or columns are emitted as ordinary cells; GFM pipe tables cannot express spans, so merged-header tables need manual repair.
  • Nested tables flatten, because a table cannot appear inside a pipe table cell.
  • Form controls and buttons are removed by sanitisation and cannot be represented in Markdown.
  • Description lists, superscript, subscript, abbreviation and disclosure elements lose their semantics and keep only their text.
  • Frames and embedded players are stripped; nothing convertible remains once their markup is gone.
  • Elements with no Markdown counterpart are passed through as raw HTML rather than deleted, so occasional tags can appear in otherwise clean output.
  • Source indentation and multiple spaces collapse per the HTML whitespace rules; only preformatted content keeps its exact spacing.

Who Converts HTML to Markdown?

  • Developers

    Migrating legacy templates and generated documentation out of HTML into a Markdown-based static site or repository, where content diffs are reviewable.

  • Technical writers

    Recovering the semantic structure of an exported help-centre article so it can be edited as Markdown rather than fought with in a rich-text field.

  • Data & retrieval engineers

    Stripping markup weight from scraped documents before chunking and embedding, so tokens are spent on prose rather than class attributes.

  • Security-conscious teams

    Converting markup from untrusted sources without executing it, and without sending internal templates to a third-party conversion endpoint.

HTML to Markdown: Comparing the Practical Approaches

MethodPrivacyGFM tablesSanitisationSetupBatch / automationCost
This tool (Turndown + DOMPurify)Highest — markup never leaves the tabYes, with pipe escaping and header synthesisBuilt in, and runs before conversionNone — paste or drop a fileOne document at a timeFree
Pandoc CLIHigh — runs locallyYes, via the gfm writerNone; it converts what you give itLocal installStrong — scriptableFree
Turndown in your own Node scriptHigh — your infrastructureOnly if you add the plugin or rulesOnly if you add a sanitiserCode plus dependenciesStrongFree
Hosted conversion APIsLow — markup is uploaded to a third partyVaries by providerOpaqueAPI keyStrongPer-request
Asking a chat model to convertLow — the markup becomes a promptUsually, but silently reformats contentNot a sanitiserNonePoor at scalePer-token

How You Can Verify the Privacy Claim

Browser-only

Zero server upload

Conversion runs inside your browser tab. Open DevTools, switch to the Network panel, and convert a file: for every format except URL to Markdown you will see no request carrying your document — because there is no endpoint to send it to.

Web Worker

Off the main thread

Heavy parsing is dispatched to a Web Worker, so a 500-page PDF or a large spreadsheet never freezes the interface. Everything is plain JavaScript — no native plugin, no WebAssembly toolchain, nothing to install.

No account

Nothing to sign up for

No login, no quota, no paywall, and no tracking tied to your files. Analytics are cookieless and aggregate only. Read the privacy policy for the full data-flow breakdown, including the one proxied exception.

The parsers doing the work

No proprietary black box: each format is handled by a widely audited open-source library, running client-side at the version pinned in our lockfile.

HTML to Markdown — FAQ

Are HTML tables converted to GFM pipe tables?

Yes. Custom rules emit pipe tables, promote the first row to a header even when the markup has no thead, escape literal pipes inside cells, and pass table sections through so the header and body stay contiguous. Row and column spans cannot be represented and become ordinary cells.

What happens to inline styles and style attributes?

They are removed during sanitisation, along with stylesheet, form, input, and button elements. Markdown has no styling layer, so only semantic structure survives — this is intentional, not a gap.

Is it safe to convert HTML from an untrusted source?

Yes. Sanitisation runs before conversion, not after, so scripts, event handlers, and javascript-scheme links are removed before anything is rendered or converted. Nothing in the input is executed at any stage.

Do code blocks keep their language for syntax highlighting?

Yes. A code element inside a pre becomes a fenced block, and the language is lifted from the conventional language- class name, so markup from a syntax highlighter keeps its tag. A pre with no code element inside is fenced without a language hint.

Why did my line breaks and indentation disappear?

Whitespace follows the HTML rules rather than your source formatting: runs of spaces and newlines inside a block collapse to a single space, and only preformatted content keeps exact spacing. Use br elements or separate paragraphs for breaks you need to keep.

In-Depth HTML Guides