For a small, rectangular HTML table you control, the browser script below exports the cell text as CSV. It rejects merged cells and nested tables rather than silently guessing how to flatten them. If you need a workbook with sheets or formatting, use the separate HTML to Excel guide.
Start with a table whose columns mean one thing
This fictional inventory table has three columns and two data rows. Save it in a local HTML file or add it to a page you control:
<table id="inventory">
<thead><tr><th>Code</th><th>Product</th><th>Note</th></tr></thead>
<tbody>
<tr><td>00742</td><td>Bracket, large</td><td>Use "A" screws</td></tr>
<tr><td>00743</td><td>Spacer</td><td>Line one<br>Line two</td></tr>
</tbody>
</table>
The product comma, quotation marks, and line break are deliberate. They expose errors that a table containing only plain words would hide.
Use the page's own export when it supplies the data you need. A browser table can contain only the current page of a longer result set. Scrolling, filtering, or pagination may change which records exist in the page. Check the intended scope before treating an export as a complete dataset.
Extract the selected table and serialize the fields
On your own local test page, open the browser's developer console. Read the code before running it. The selector #inventory targets the table above; change it only when you have identified another specific table.
function tableToCSV(table) {
if (!(table instanceof HTMLTableElement)) {
throw new Error("Choose an existing HTML table.");
}
if (table.querySelector("table")) {
throw new Error("Nested tables need a separate export rule.");
}
const rows = Array.from(table.rows, row => {
return Array.from(row.cells, cell => {
if (cell.rowSpan !== 1 || cell.colSpan !== 1) {
throw new Error("Expand merged cells before exporting.");
}
return cell.innerText.replace(/\r\n?/g, "\n");
});
});
if (!rows.length || !rows[0].length) {
throw new Error("The table is empty.");
}
const columns = rows[0].length;
if (rows.some(row => row.length !== columns)) {
throw new Error("Rows have different column counts.");
}
const quote = value => '"' + value.replace(/"/g, '""') + '"';
return rows.map(row => row.map(quote).join(",")).join("\r\n");
}
const csv = tableToCSV(document.querySelector("#inventory"));
console.log(csv);
This code uses the table's row and cell collections, whose browser interfaces are documented by MDN's table rows reference and table cell reference. It takes rendered cell text; it does not export links' target addresses, input values, colors, or formulas. A table with hidden records or form controls needs a deliberate extraction rule for those values.
Every field is quoted. A quotation mark inside a field becomes two quotation marks, and records use CRLF separators. These rules follow the common CSV format described in RFC 4180. The line break inside the final note remains inside its quoted field.
Compare the CSV with the original cells
The fixture produces this CSV text:
"Code","Product","Note"
"00742","Bracket, large","Use ""A"" screws"
"00743","Spacer","Line one
Line two"
There are three records including the header, with three fields in each. There are four visible lines because the last field contains a line break. Counting newline characters is therefore not a valid record-count check.
Copy the CSV string value into a plain text editor and save it as UTF-8 with a .csv extension. If the developer console shows a string representation with surrounding quotes or literal escape sequences, copy the actual value rather than that representation.
Import the file into the receiving tool using comma as the delimiter and double quotation marks as the text qualifier. Compare the awkward cells first: Bracket, large belongs in one column, Use "A" screws should contain one pair of quotation marks, and the two note lines belong in a single cell. Confirm the record count with a CSV-aware importer.
The code preserves 00742 as text in the CSV. A spreadsheet may still interpret it as the number 742. Choose text for the Code column during import when the receiving tool provides that option. Opening CSV correctly explains that separate import step.
Stop when the table needs a different rule
If a heading spans three columns, decide which header each exported field should receive before running a conversion. If a category cell spans several rows, decide whether to repeat its value in each record. Those are data decisions; silently inserting blanks can change the meaning.
CSV quoting also does not make untrusted content safe to evaluate in a spreadsheet. If the source includes formula-like values, use an import process that treats those fields as text and check the receiving application's guidance. Keep the faithful data export separate from any intentionally altered copy.
Finish by comparing the imported field values with the source table, then retain the source URL or filename and export date beside the data. That gives the next person both usable records and a way to check where they came from.