Getting the rows out is the easy half. Whether the file opens cleanly on the other side comes down to a handful of details that are easy to get wrong and annoying to fix afterwards. These are the ones that actually cause trouble.
Write a byte order mark. A UTF-8 CSV with no BOM opens in Excel as the system code page, which turns accented and Chinese characters into mojibake. Three bytes at the front (EF BB BF) fix it, and every other tool ignores them. This is the single most common reason a CSV "opens wrong" in Excel and fine everywhere else.
Pick the delimiter your Excel expects. Excel uses the system list separator, which is a comma in en-US and en-GB and a semicolon in German, French, Spanish, Italian and most of continental Europe. Send a comma-separated file to a colleague in Berlin and every row lands in column A. If you do not know where the file is going, tab-separated is the safest, because Excel reads tabs the same way everywhere.
Neutralise cells that start with =, +, - or @. A spreadsheet treats those as the start of a formula, so a cell a web page put there can execute when someone opens the file. This is CSV injection, and the usual fix is to prefix the cell with an apostrophe or a tab so it is read as text.
Quote properly. A field containing a comma, a quote or a newline has to be wrapped in double quotes, with internal quotes doubled. Most breakage in hand-rolled exporters is a cell with a comma in it.
Leading zeros. 00734 in a CSV becomes 734 in Excel, because Excel type guesses on open. There is no way to prevent it from the CSV side alone; the reliable route is importing through Power Query with the column typed as Text, or exporting to .xlsx instead, where the cell type is stored in the file.
The useful shape is an array of objects keyed by the table headers:
[
{ "Ticker": "AAPL", "Last": "228.14", "Change": "+1.2%" },
{ "Ticker": "MSFT", "Last": "417.00", "Change": "-0.4%" }
]
Three decisions to make deliberately:
What to do about bad headers. Real tables have empty header cells, duplicated header text, and headers with newlines and sort arrows in them. Keys have to be unique, so decide up front: blank becomes column_3, a duplicate becomes Last_2, whitespace collapses to a single space.
Whether to convert types. Leaving everything as a string is the honest default, because "1,234.50" and "12%" and "2026-09-05" all need a decision that depends on the source. If you do convert, do it as a separate, visible step rather than silently.
Whether you want links. The visible text of a cell often hides the thing you actually need, which is the href. If so, pull it into its own field rather than mangling the text.
An array of arrays with a separate header row is the better shape when the table is genuinely positional, such as a matrix, or when duplicate headers are meaningful.
The format is forgiving except in three places:
Pipes inside cells have to be escaped as \|, or the row gains a column and the whole table stops rendering.
Newlines inside a cell are not allowed. A Markdown table row is one line. Replace internal newlines with a space or with <br>, depending on whether your renderer allows inline HTML.
The separator row sets alignment, and it is required:
| Ticker | Last | Change |
|---|---:|---:|
| AAPL | 228.14 | +1.2% |
A colon on the right aligns right, which is what you want for numbers. Column widths do not need to line up in the source; only the pipes matter.
GridPick handles all of the above by default: CSV with a BOM and a choice of comma, semicolon, tab or pipe; formula-injection prefixes on cells that need them; real .xlsx output with numbers stored as numbers and headers in bold; JSON keyed off the table's own headers with duplicates resolved; and escaped Markdown. It also has an option to strip currency symbols, thousands separators and percent signs so a spreadsheet reads the column as numbers, and one to pull link URLs into their own column.
The free version covers clipboard copy and CSV with no row limit. Excel, JSON and Markdown are in Pro, which is a one-time payment.
If you would rather do it yourself, the pieces above are the checklist.
GridPick does this for you. It reads real tables and div-based grids, expands merged cells, warns you when a grid is only half loaded, and exports to Excel, CSV, JSON or Markdown. The free version has no row limit; Pro is a one-time payment.