How to Convert an HTML Table to Markdown Without Hand-Fixing Rows
Copy a table off a web page and turn it into a clean GitHub-flavoured Markdown table: th becomes the header row, pipes get escaped, and cells flatten to text.
How to Convert an HTML Table to Markdown Without Hand-Fixing Rows
You find a comparison table on a docs page, you want it in your README, and the obvious move is to copy it and paste it in. Then you hit the wall: a Markdown table is not HTML. It needs a header row, a |---| separator line, plain text in every cell, and no stray pipes. Doing that by hand for a ten-row table is tedious and easy to get wrong by one column.
Going from an HTML table to a Markdown table is mostly mechanical, so it should be a paste-and-copy job, not a retyping job. This guide walks through what actually has to change, shows one full input-to-output example, and points you at a tool that does the whole thing in the browser.
What separates an HTML table from a Markdown one
An HTML <table> is a tree: <tr> rows hold <th> or <td> cells, and those cells can contain anything, including bold tags, links, nested divs, even another table. A Markdown table is much stricter. It is a flat grid of pipe-delimited rows where every cell holds only inline text, and the second line is always a separator that defines the columns.
So three things have to happen on the way over:
- The first row has to become a header, because a Markdown table cannot exist without one.
- A separator row of dashes has to be inserted right under the header.
- Every cell has to be reduced to plain text and made pipe-safe.
Miss any of those and the table either fails to render or shifts a column. The rest of this post is really just those three rules in detail.
The header rule: th becomes the header row with a pipe separator
This is the one detail people most often get wrong by hand. In Markdown, the header is not a style choice — it is structural. The first content row is the header, and the line directly beneath it, made of pipes and dashes, is what tells the renderer "this is a table and it has this many columns."
Concretely, a <tr><th>Name</th><th>Age</th></tr> turns into two output lines:
| Name | Age |
| --- | --- |
That second line, | --- | --- |, is the separator row. One --- segment per column. If your source table has no <thead> and no <th> cells at all, the very first <tr> is promoted to the header instead, because the grid still needs one. So a bare table of <td> cells does not break; its top row just becomes the labels. If your real header sits lower in the markup, reorder the rows in the source before converting.
Get the separator wrong — too few dashes, a missing pipe — and the whole block renders as literal text. That is why letting a converter emit the separator row is worth more than it looks.
Cleaning up the cells
HTML cells are permissive; Markdown cells are not. Four kinds of cleanup matter:
Strip the tags, keep the text. A cell like <td><b>4K</b> monitor</td> should become the text 4K monitor, and <a href="/x">link</a> should become link. Markdown table cells render inline content, not block HTML, so the wrappers come off and only the readable text survives. Script and style blocks get dropped whole.
Decode the entities. Page source is full of &, ' and . Those should turn back into &, ' and a real space, otherwise your Markdown shows the raw entity codes.
Escape the pipes. This is the silent table-breaker. A raw | inside a cell ends that cell early and pushes every column after it out of place. A cell holding Cable A | B has to come out as Cable A \| B so it renders as literal text and the column count stays correct. Entities get decoded first, then any pipe that falls out of the decode gets escaped too.
Collapse line breaks. A <br> or a newline inside a cell usually wants to become a single space so the row stays on one line; if a cell genuinely needs two lines, keeping it as a literal <br> works on most renderers.
Optionally, alignment carries over too: a <th align="right"> maps to ---:, align="center" maps to :---:, and align="left" maps to :---. Columns with no alignment signal stay as plain ---, since Markdown defaults to left.
A worked example
Here is a small but realistic HTML table — the kind you would copy out of a spec page, complete with a bold tag, a link, an entity and a pipe:
<table>
<tr><th>Tier</th><th align="right">Price</th><th>Notes</th></tr>
<tr><td><b>Free</b></td><td align="right">$0</td><td>Up to 500 calls & 1 seat</td></tr>
<tr><td>Pro</td><td align="right">$9</td><td>Region A | B routing</td></tr>
</table>
With alignment reading on, that converts to:
| Tier | Price | Notes |
| --- | ---: | --- |
| Free | $0 | Up to 500 calls & 1 seat |
| Pro | $9 | Region A \| B routing |
Walk through what changed. The <th> row became the header with its separator underneath. The align="right" on the price column produced ---:, so prices right-align in any GitHub-style renderer. <b>Free</b> flattened to Free. The & decoded to &. And the pipe in Region A | B became Region A \| B, which keeps the third column intact instead of inventing a fourth one. Nothing in that output needs hand-editing before it drops into a README.
How I actually do this now
I migrated an old spec doc out of a CMS last month — about thirty tables, each one an HTML blob wrapped in nested <div> and <span> junk from the CMS editor. My first instinct was to clean one up by hand to see how bad it was. Fifteen minutes for a single eight-row table, and I still missed an escaped pipe in a version string that broke the column on GitHub. After that I stopped pretending it was a typing job. I pasted each <table> block into the HTML Table to Markdown converter, let it strip the wrappers, decode the entities and emit the separator row, and copied the result straight into the docs repo. The thirty tables took less time than that first one did by hand, and the reviewer reading the diff saw faithful copies, not my transcription typos. The whole thing runs in the browser, so none of the internal table content left my machine.
If your starting point is not HTML at all — say you are building a table from scratch or jotting one down by hand — the Markdown Table Generator is the better fit, since it gives you an editable grid instead of expecting source markup to parse.
When this approach falls short
Two honest limits. First, a Markdown cell cannot hold block HTML. If a source cell contains a nested list, a paragraph, or another whole table, that structure is gone and the cell flattens to text — restructure the original if you need the layout to survive. Second, by default a converter grabs the first table in source order, which is not always the first one you see rendered on screen. If you get the wrong one, paste only the exact <table>...</table> block you want, or turn on a convert-every-table option and pick the right block out of the output.
Within those limits, the conversion is dependable precisely because it is mechanical: header promotion, separator insertion, tag stripping, entity decoding, pipe escaping. Once those five steps are automated, copying a table from a web page into a Markdown doc stops being a chore and becomes a paste.
Made by Toolora · Updated 2026-06-13