HTML Table to CSV and JSON
Pull a table out of HTML and convert it to CSV, TSV, JSON or Markdown, keeping nested tables separate and showing where merged cells break the grid.
A nested table used to leak into its parent — that is fixed
Finding the rows of a table means searching inside it, and a search inside an element finds everything below it rather than only its own children. A table with another table in one of its cells therefore claimed the inner table's rows as its own, and each of those rows claimed the inner cells too. Nested tables are rare in hand-written HTML and everywhere in email templates and older sites, which is exactly the kind of HTML people paste into an extractor.
| Outer table | Inner table | |
|---|---|---|
| Written as | 2 rows | 2 rows |
| Extracted before | [outer] [inner1inner2 | inner1 | inner2] [inner1] [inner2] | [inner1] [inner2] |
| Extracted now | [outer] [inner1inner2] | [inner1] [inner2] |
The inner rows appeared twice — once as themselves and once inside their parent — and one cell held the inner table's entire contents run together. Rows and cells are now kept only if the nearest table or row above them is the one being read. All 7 of the ordinary tables below — headers, spans, ragged rows, a thead/tbody split, two tables side by side — extract to exactly what they did before.
One thing remains and is not a bug: the outer cell still reads as the inner table's words run together, because that text genuinely is the content of that cell. A cell containing a table contains its words.
A row of cells is not a row of columns
A cell with colspan="2" is still one cell, so the row comes out one
short. The CSV then has a three-column header over a two-column row, and whatever
reads it lines the wrong values up under the wrong headings. rowspan
does the same thing one row later — the spanned row is missing the cell the row
above is still occupying.
| Table | Extracted | Cells per row | Columns you see |
|---|---|---|---|
| Plain table | [A | B] [1 | 2] | 2, 2 | 2 |
| colspan in the body | [A | B | C] [wide | 3] | 3, 2 | 3 |
| colspan in the header | [Both] [1 | 2] | 1, 2 | 2 |
| rowspan | [tall | a] [b] | 2, 1 | 2 |
| Ragged rows | [1] [1 | 2 | 3] | 1, 3 | 3 |
Expanding spans properly means building a real grid and filling it, which is a different and much larger piece of code. Worth knowing the output is cells as written rather than the grid you see.
The column count in the dropdown is the first row's
The label reads "N rows × M cols", and M is the length of row zero. When rows disagree — which is exactly what a span causes — it describes the header and not the table.
| Table | Label says | Widest row | Columns you see |
|---|---|---|---|
| colspan in the header | 1 cols | 2 | 2 |
| Ragged rows | 1 cols | 3 | 3 |
A header spanning two columns over two body columns is labelled one column wide. The row count is always right; the column count is right when the table is rectangular, which is when you did not need to be told.
One small dead branch in the CSV writer
A cell is quoted if it contains a comma, a quotation mark, or a newline. The newline case can never happen: cell text has already had its whitespace squeezed to single spaces, so a cell that spanned two lines in the HTML arrives as one line.
| Cell | Written as |
|---|---|
plain | plain |
has,comma | "has,comma" |
has"quote | "has""quote" |
(empty) | (empty) |
A cell written across two lines arrives as line1 line2, so
it is never quoted for a newline. Harmless, and worth noticing — it is the kind
of branch that looks like it is protecting you and is not doing anything at all.
How to use
- Paste the HTML containing the table.
- Pick which table if the page has several.
- Choose your output format.
- Check any rows where cells were merged.
Frequently asked questions
Why not just copy and paste the table?
Sometimes that works. It fails when the page uses styling that copies as a single block, when cells contain hidden markup, or when the table is long enough that selecting it accurately is painful. Extracting from the HTML is reliable where selection is not.
How are merged cells handled?
They have to be expanded, because CSV and JSON have no concept of a cell spanning rows or columns. The usual approach is to repeat the value across every position it covered, which preserves the grid at the cost of introducing duplicates that were not in the original.
What happens to nested tables?
They are the hardest case and rarely convert well. A table inside a cell has no flat representation, so it is normally flattened to its text content. Nested tables were common in older layout-driven HTML and are rare in modern markup.
Which output format should I choose?
CSV for spreadsheets, JSON if the data is going into code, Markdown for documentation, and TSV when the data contains commas and you want to avoid quoting. TSV pastes into spreadsheets particularly cleanly.
Does the header row get detected automatically?
Where the markup uses proper header cells, yes. Many tables style their first row instead of marking it up semantically, in which case the header has to be identified by position. This is one of several reasons semantic markup is worth writing.
Does my data get uploaded?
No. Parsing happens entirely in your browser, so HTML containing personal or internal data never leaves your machine.
🔒 This tool runs entirely in your browser. Nothing you enter is uploaded, logged, or stored.