Convert HTML to GFM Markdown and see what was left out
This converter turns the headings, paragraphs, lists, tables, code and links of an HTML document into GitHub Flavored Markdown (GFM). <h2> becomes ##, <strong> becomes **, a link becomes [text](url), and a table becomes a Markdown table with its columns split by |. Unlike a tag stripper, it keeps the structure of the document, so the result can go straight into a GitHub README or a notes app that reads Markdown.
You can paste HTML code, or paste text you copied from a web page or Google Docs. Copied text arrives with the HTML formatting the browser puts on the clipboard, and that is what gets converted. Below the result, a structure map shows the order of blocks in the document, and a conversion log lists what Markdown could not carry as it was, such as merged cells, relative URLs and embedded videos.
When you need to turn HTML into Markdown
- Moving a blog or CMS post elsewhere: CMS posts, WordPress included, are stored as HTML. Converting them is faster than retyping headings, lists and links when you move to a static site generator or a Markdown notes app.
- Turning a web page into a README or developer doc: paste a page with install steps and the code block languages (
language-bash) and task-list checkboxes come through as GFM. - Moving Google Docs or meeting notes into Markdown notes: text copied from a document editor often writes bold and italic as style values instead of tags. The converter reads those values and writes
**bold**and*italic*. - Feeding a web page into an AI tool or a note in less space: without class and style attributes and scripts, the same content is much shorter. In the examples above, "Copied from a document editor" shrinks from 1,057 bytes to 217, and "Newsletter email" from 997 to 362.
- Archiving a newsletter or email: email HTML is full of layout tables and 1×1 images that report when it was opened. Layout tables are unwrapped into text and tracking images are dropped, which leaves a readable document.
How to use it
- Add your HTML: paste code into the HTML box, or select the part of a web page you want, copy it (Ctrl+C) and paste. Open an HTML or HTM file with "Open file" or drop it onto the box. While an example is loaded, pasting replaces it.
- Check how it was pasted: text copied from a page goes in as HTML formatting, while HTML code copied from a code editor goes in as plain characters. If it picked the wrong one, click "Paste as plain text" or "Paste with formatting" in the status line.
- Choose a Markdown style: heading marker (
#or underline), list bullet (-*+), emphasis marker, code fence, link style and table format. The result is rewritten as soon as you change a setting. - Set a base URL: if the HTML has relative URLs such as
/images/a.jpg, enter the address of the original page in the base URL box to make them full URLs. A base, canonical or og:url address in the HTML is filled in automatically. - Review and take it: check the preview, click a band of the structure map to jump to that part of the result, and read the conversion log. Use "Copy Markdown" or "Save .md". Ctrl+Enter (⌘Enter) in the result box also copies.
How each tag is converted
These are the results the converter actually produces with its default settings (headings #, bullets -, emphasis *, fence ```, inline links, Markdown tables).
| HTML | Markdown | Notes |
|---|---|---|
<h2>Setup</h2> | ## Setup | h1 to h6 become one to six #. The underline style only covers levels 1 and 2. |
<strong>bold</strong> <em>italic</em> | **bold** *italic* | The b and i tags, style font-weight of 600 or more and font-style:italic give the same result. |
<del>old</del> | ~~old~~ | GFM strikethrough. The s and strike tags and line-through work too. |
<code>npm i</code> | `npm i` | If the code contains a backtick, it is wrapped in a longer run of backticks. |
<a href="https://example.com">Example</a> | [Example](https://example.com) | When the text and the URL are the same, it is shortened to <https://…>. |
<img src="a.png" alt="Chart"> |  | The image file is not included; only its URL is kept. |
<ul><li>Apples</li><li>Pears</li></ul> | - Apples- Pears | Nested lists are indented by the width of the parent bullet. |
<ol start="3"><li>Third</li></ol> | 3. Third | The start number is kept. |
<li><input type="checkbox" checked> Done</li> | - [x] Done | A checkbox at the start of a list item becomes a GFM task list item. |
<blockquote>Quote</blockquote> | > Quote | Lists and code inside a quote get a > on every line. |
<pre><code class="language-js"> | ```js … ``` | The language name is taken from a language- or lang- class. |
First line<br>Second line | First line␣␣Second line | Two spaces at the end of a line make a line break. Inside a table cell it stays <br>. |
H<sub>2</sub>O | H<sub>2</sub>O | sup, sub, kbd, mark and u have no GFM syntax, so the tags are kept. |
<hr> | --- | A horizontal rule. |
Where Markdown breaks in everyday English text
Markdown symbols work or fail depending on the characters around them, so plain text that merely looks harmless can change meaning. The "Written with plain symbols" column shows what happens when you only swap the tags for symbols; each one was rendered with markdown-it (CommonMark 0.31.2) and marked (GFM) to check. The converter adds a backslash only where one is needed. Languages that attach particles to words, such as Korean and Japanese, hit a further problem with ** next to punctuation, which the converter handles by writing <strong>.
| Original | Written with plain symbols | This converter |
|---|---|---|
| 1986. A great year | 1986. A great year → CommonMark renders a numbered list that starts at 1986, and the sentence becomes a list item. | 1986\. A great year |
| Walk 10~15 km or 20~30 km | Walk 10~15 km or 20~30 km → in GFM, "15 km or 20" is struck through, because GFM also reads a pair of single tildes as strikethrough. | Walk 10\~15 km or 20\~30 km |
| [Note](optional) before you start | [Note](optional) → "Note" turns into a link pointing to "optional". | [Note\](optional) before you start |
| Set snake_case_name first | Set snake_case_name first → renders unchanged as a paragraph, because an underscore inside a word does not start emphasis. | Set snake_case_name first (no backslash) |
The last row is deliberate: a backslash that is not needed only makes the Markdown harder to read, so the converter leaves it out. The same goes for a # that is not at the start of a line, as in C#. If you pick _ as the emphasis marker, only the spots where it would not work inside a word, such as un__break__able, are written with ** instead.
Pasting from a web page or Google Docs
When you copy text in a browser, the clipboard holds both the visible characters (text/plain) and the formatted version (text/html). The converter reads both at the moment of pasting. If the plain text visibly contains tags, it treats the paste as HTML code; if not, it treats it as copied page content. So dragging across a web page brings in that part's HTML, while copying HTML code from a code editor brings in the code characters rather than the editor's syntax colors.
Google Docs wraps the whole copied range in <b style="font-weight:normal" id="docs-internal-guid-…"> and writes real bold and italic as font-weight:700 and font-style:italic on spans. Several editors have been reported to read that wrapper b tag as bold, so everything you paste ends up wrapped in **…**. This converter does not treat a b tag with font-weight:normal as bold, and carries bold, italic, strikethrough and superscript over from the style values of the spans inside. The example "Copied from a document editor" above is exactly this kind of HTML.
Things to know when converting tables
A GFM table needs a header row first, then a delimiter row of ---. The colons in the delimiter row set alignment: :--- is left, ---: is right and :---: is centered. The converter takes alignment from the align attribute or style text-align, and pads the cells with spaces so the columns also line up in the raw Markdown (a column wider than 40 characters is not padded).
- A | inside a cell: written as
\|so it is not mistaken for a cell border. Per the GFM spec, a | inside code or a link must be written the same way. - Several lines in one cell: GFM table cells cannot hold blocks such as paragraphs or lists, so the lines are joined with
<br>. - Merged cells: a Markdown table cannot merge cells. By default the merged spots are spread out as empty cells; if you switch the table setting to HTML, the table stays as an HTML table with its rowspan and colspan, minus class and style.
- Tables without a header: since a header row is required, the first row is used as the header. The conversion log reports how many.
- Layout tables: a table with role="presentation", a table inside a table, or a table with a single column is treated as page layout rather than data, and its cells are unwrapped into text one after another. This is common in email HTML.
Choosing a Markdown style
| Setting | Values | When to pick it |
|---|---|---|
| Headings | # Heading / underline | Underlines (===, ---) only exist for levels 1 and 2, so level 3 and up use #. For most documents the # style is easier to read and edit. |
| Lists | - * + | The rendered result is the same. Two lists in a row with the same bullet merge into one, so put <!-- --> between them to keep them apart. |
| Emphasis | * / _ | * works inside words and next to punctuation, while _ does not work inside a word. Even if you pick _, the spots where it would fail are written with *. |
| Code | ``` / ~~~ / indented | Only the fence styles keep the language name (```js). If the code contains the same fence, a longer one is used. |
| Links | Inline / reference | Reference style is easier to read when URLs are long. The text gets [text][1], the document ends with [1]: url, and links with the same URL and title share one number. |
| Tables | Markdown / HTML | HTML keeps the shape of tables with merged cells or long lists inside a cell. Choose Markdown when the destination strips HTML. |
How much smaller does it get?
Removing styling attributes, scripts and layout tables cuts the size a lot, even though the content is the same. These are the values the size bar shows when you open each example (bytes in UTF-8). Document-editor HTML, which is full of style values, and email HTML shrink the most.
| Example | HTML | Markdown | Smaller by |
|---|---|---|---|
| Blog post | 842 B | 543 B | 36% |
| Table with merged cells | 671 B | 428 B | 36% |
| Developer docs | 795 B | 511 B | 36% |
| Copied from a document editor | 1,057 B | 217 B | 79% |
| Newsletter email | 997 B | 362 B | 64% |
Common mistakes
- Pasting the whole page source and getting the menus too: view-source code includes the top navigation and footer links. If you only need the article, select just that part on the page, copy it and paste.
- Publishing relative URLs as they are:
/uploads/a.jpgonly opens on the original site. Enter a base URL to turn it into a full URL. - Thinking the images are missing from the preview: the preview does not load images, for privacy and speed, and shows an "Image" marker with the alt text instead. A
in the result is correct. - Reading an empty cell in a Markdown table as a missing value: it may be a spot where a merged cell was spread out. If the log mentions merged cells, compare with the original table or keep the table as HTML.
- Deleting the two spaces at the end of a line: a line break (br) is written as two trailing spaces. An editor that trims trailing whitespace on save will remove the line breaks.
- Pasting into a place that does not accept HTML tags: superscript, keys, collapsible sections, and HTML tables are written as tags. In an editor that strips HTML, the text stays but the formatting is lost.
Limits
- Markdown describes document structure, so CSS color, fonts, spacing and layout are not carried over.
- It does not fetch a page from a web address (URL). Content that only appears after logging in, or that a script fills in later, has to be copied from the screen and pasted.
- Image and video files do not go into the .md; only their URLs remain. Images embedded in the page as data: URLs are dropped because they make the result far too long.
- iframe, video and audio remain only as links to their URLs, and forms (input fields, buttons) and SVG graphics are not carried over.
- HTML from a word processor that writes lists as paragraphs starting with a bullet character may come out as paragraphs, not a list.
- Input is read up to 2 MB. How the result looks on GitHub follows the GFM spec; editors differ in how they handle HTML tags and line breaks.
Sources
Basis: Markdown syntax comes from the GFM 0.29-gfm and CommonMark 0.31.2 specifications, and browser behavior from MDN. All were checked on 22 October 2026. The renders in the table above were produced with markdown-it (CommonMark) and marked (GFM), and the Markdown in the tables is this tool's own output.
- 01GitHub Flavored Markdown Spec 0.29-gfm4.10 Tables (delimiter row, alignment, \| inside a cell even in code, example 200, no blocks), 5.3 Task list items, 6.5 Strikethrough (one or two tildes, example 491), 6.11 Disallowed raw HTML
- 02CommonMark Spec 0.31.2Left- and right-flanking rules for emphasis, backslash escapes, ordered list numbers (up to 9 digits), hard line breaks
- 03MDN — DOMParser.parseFromString()Scripts in a document parsed as text/html are not executed
- 04MDN — Element: paste eventReading pasted content with clipboardData.getData() and changing the default behavior
- 05RFC 7763 — The text/markdown Media TypeThe media type name for .md files, text/markdown (March 2016)
- 06MarkText #4688 — Pasting from Google Docs makes the whole paragraph boldThe Google Docs docs-internal-guid b wrapper and span font-weight values
Frequently asked questions
How is this different from an HTML tag remover?
A tag remover keeps only the text. This converter turns h2 into ##, strong into **, a link into [text](url) and a table into a Markdown table split by |, so the structure of the document survives. The result can be pasted straight into a GitHub README or a notes app that uses Markdown.
Can I enter a web page address (URL) and convert it directly?
No. The tool does not go through a server, so it does not fetch HTML from other sites. Select the part of the page you want, copy it and paste it into the HTML box, and the page formatting arrives as HTML. For the whole page, copy the code from your browser's view-source page (Ctrl+U) and paste that.
Won't text copied from Google Docs all turn bold?
No. Google Docs wraps the whole copied range in a b tag with font-weight:normal and writes real bold and italic as span style values (font-weight:700, font-style:italic). This converter does not treat the wrapper as bold; it reads the style values and carries over bold, italic and strikethrough.
Why does a <strong> tag stay in some places where I expected bold?
In CommonMark, a closing ** directly after punctuation and directly before a letter is not read as emphasis, as in **"quote"**s or **(note)**s. That happens mostly in languages that attach particles to words. Writing <strong>"quote"</strong>s as a tag keeps it bold on GitHub and similar places.
Why is there a backslash before the tilde in 10~15?
GFM also reads text wrapped in a pair of single tildes as strikethrough (spec example 491). With two tildes, as in '10~15 km, 20~30 km', the text between them could look crossed out, so the converter writes \~. It still displays as ~.
How are tables with merged cells (rowspan, colspan) handled?
A Markdown table cannot merge cells, so by default merged spots are spread out as empty cells. To keep the shape, switch the table setting to HTML. The table stays as HTML without class and style, so places that allow HTML, such as a GitHub README, show the merged cells as they were.
How do I turn relative URLs like /images/a.jpg into full ones?
Enter the address of the original page in the base URL box and relative URLs become full ones such as https://…/images/a.jpg. If the HTML contains a base, canonical or og:url address, it is filled in automatically. Without a base, relative URLs stay as they are and the conversion log gives the count.
Is the HTML I enter sent to a server or stored?
No. Pasting, reading files, converting and previewing all happen in your browser, and nothing is left in the address bar or browser storage. Scripts in the HTML are not run, and the preview loads no images, showing only the alt text.