CSV to Markdown Table

Turn a CSV into a Markdown pipe table you can paste into a README, a pull request or a wiki page. Commas inside quotes stay in their cell, stray pipes get escaped, and Pad columns lines up the columns so the raw file is readable too. The CSV is parsed in this tab and never uploaded.

CSV Input

1 CSV to Markdown Table example

Input CSV

package,version,license,notes
astro,5.2.0,MIT,"Static sites, islands"
tailwindcss,4.0.1,MIT,Utility-first CSS
left-pad,1.3.0,WTFPL,"Unpublished in 2016, broke half of npm"
zod,3.24.1,MIT,Schema | validation

Markdown output (padded)

| package     | version | license | notes                                  |
| ----------- | ------- | ------- | -------------------------------------- |
| astro       | 5.2.0   | MIT     | Static sites, islands                  |
| tailwindcss | 4.0.1   | MIT     | Utility-first CSS                      |
| left-pad    | 1.3.0   | WTFPL   | Unpublished in 2016, broke half of npm |
| zod         | 3.24.1  | MIT     | Schema \| validation                   |

The quoted commas in the notes column stayed inside their cells, and the pipe in the last row became \| so it does not start a fifth column.

2 How alignment works in a pipe table

Alignment lives in the separator row under the header. A colon on the left means left-aligned, a colon on the right means right-aligned, colons on both sides mean centered, and no colon leaves it to the renderer (GitHub uses left).

| left | center | right | none |
| :--- | :----: | ----: | ---- |
| a    |   b    |     c | d    |

Align: numbers right checks each column and right-aligns it only when every non-empty value looks like a number (42, -3.5, 1,200.50, 15%). Versions such as 5.2.0 are not numbers and stay as they are.

Untick Pad columns when the table lives in a file that changes often. Renderers ignore the padding, and without it, making one cell the longest in its column changes one line in the diff instead of every row in the table.

3 What Markdown tables cannot do

No multi-line cells

Every row has to fit on one line. The tool writes line breaks inside a quoted CSV field as <br>, which GitHub, GitLab and most renderers display as a break.

A header is mandatory

GFM tables need a header row. Untick First row is header and the tool writes Column 1, Column 2 and so on, then treats every CSV line as data.

No merged cells

There is no colspan or rowspan. Short rows are padded with empty cells so every row has the same count.

Not every flavor has them

Pipe tables are a GitHub Flavored Markdown extension. Original Markdown and plain CommonMark render them as text with pipes in it.

How to use the CSV to Markdown Table tool

  1. Paste CSV into the CSV Input pane, or click Upload CSV to read a .csv or .tsv file from your disk. Dummy Data loads a sample.
  2. Leave the delimiter on Auto-detect, or pick Comma, Semicolon, Tab or Pipe. The header shows which one was detected.
  3. Choose an alignment from the Align dropdown and decide whether to keep Pad columns on. Untick First row is header if your CSV has no header line.
  4. Check the result in the Markdown tab, or switch to Preview to see it rendered as a table.
  5. Click Copy Markdown and paste the table where you need it. Clear empties both panes.

How to convert CSV to a Markdown table in JavaScript, Python and PHP

JavaScript / Node.js

const csv = `name,notes
Ada,"Wrote the first program, 1843"
Zod,Schema | validation`;

// Minimal RFC 4180 parser: quotes, "" escapes, separators and newlines inside quotes
function parseCsv(text, sep = ",") {
  const rows = [];
  let row = [], field = "", q = false;
  for (let i = 0; i < text.length; i++) {
    const c = text[i];
    if (q) {
      if (c === '"' && text[i + 1] === '"') { field += '"'; i++; }
      else if (c === '"') q = false;
      else field += c;
    } else if (c === '"') q = true;
    else if (c === sep) { row.push(field); field = ""; }
    else if (c === "\n") { row.push(field); rows.push(row); row = []; field = ""; }
    else if (c !== "\r") field += c;
  }
  row.push(field); rows.push(row);
  return rows;
}

// Escape backslashes and pipes, and keep each row on one line
const cell = (s) => s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
const line = (r) => `| ${r.map(cell).join(" | ")} |`;

const [head, ...body] = parseCsv(csv);
const md = [line(head), `| ${head.map(() => "---").join(" | ")} |`, ...body.map(line)].join("\n");
console.log(md);

The cell() escape order matters: backslashes first, then pipes, or the backslash added in front of a pipe gets doubled.

Python

import csv
import io

data = """name,notes
Ada,"Wrote the first program, 1843"
Zod,Schema | validation
"""

def cell(s):
    return s.replace("\\", "\\\\").replace("|", "\\|").replace("\r\n", "<br>").replace("\n", "<br>")

rows = list(csv.reader(io.StringIO(data)))
head, body = rows[0], rows[1:]

lines = ["| " + " | ".join(cell(c) for c in head) + " |",
         "| " + " | ".join("---" for _ in head) + " |"]
lines += ["| " + " | ".join(cell(c) for c in r) + " |" for r in body]
print("\n".join(lines))

csv.reader already handles quoted fields, doubled quotes and line breaks inside quotes.

PHP

<?php
$csv = "name,notes\nAda,\"Wrote the first program, 1843\"\nZod,Schema | validation\n";

// fgetcsv on a memory stream handles quoted fields that span lines, str_getcsv does not
$fh = fopen('php://memory', 'r+');
fwrite($fh, $csv);
rewind($fh);
$rows = [];
while (($r = fgetcsv($fh, null, ',', '"', '')) !== false) {
    if ($r !== [null]) $rows[] = $r;
}

$cell = fn($s) => str_replace(["\\", '|', "\r\n", "\n"], ["\\\\", '\|', '<br>', '<br>'], $s);
$line = fn($r) => '| ' . implode(' | ', array_map($cell, $r)) . ' |';

$head = array_shift($rows);
$out = [$line($head), '| ' . implode(' | ', array_fill(0, count($head), '---')) . ' |'];
foreach ($rows as $r) $out[] = $line($r);
echo implode("\n", $out), "\n";

The empty fifth argument to fgetcsv turns off PHP's non-standard backslash escape, which otherwise misreads a field ending in a backslash.

Common errors and how to fix them

The whole table renders as one line of text with pipes in it
Pipe tables need a blank line before them and a separator row (| --- |) right after the header. Also check that the target supports GFM, since plain CommonMark and original Markdown have no tables.
A row has an extra column or text is cut off
A literal | in the data was not escaped. This tool writes it as \|, but if you edited the table by hand afterwards, escape any new pipes the same way.
Everything ended up in one column
The delimiter is wrong. European Excel exports use semicolons; pick Semicolon, or leave Auto-detect on and check what it detected.
Half the file is inside a single cell
There is an unbalanced double quote in the CSV, so the parser is still inside a quoted field. The status line says Unclosed quote when this happens; find the stray quote and double it or remove it.
<br> shows up as literal text
Some renderers (Bitbucket, a few static site generators with raw HTML disabled) do not allow HTML in Markdown. Replace the line breaks in the source data with a space or a separator before converting.

Good to know

Pipe tables are defined by the GitHub Flavored Markdown spec, not by CommonMark itself. GitLab, Gitea, Obsidian, most static site generators and pandoc (as pipe_tables) accept the same syntax.

Specifications and references

This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.

Frequently Asked Questions

How do I convert a CSV file to a Markdown table?

Paste the CSV or click Upload CSV. The Markdown pane fills in as you type; click Copy Markdown and paste it into a README, issue or wiki page.

How are commas and pipes inside a cell handled?

A field wrapped in double quotes keeps its commas, and a doubled quote ("") becomes one quote. A literal | in a cell is written as \| so it does not start a new column.

What happens to line breaks inside a CSV field?

They become <br> tags, because a Markdown table row has to stay on one line. GitHub, GitLab and most other renderers display <br> as a line break inside the cell.

How do I right-align or center a column?

Pick an option from the Align dropdown. The alignment goes in the separator row as colons (:---, :---:, ---:). Align: numbers right only right-aligns columns where every value is a number.

Does the padding matter?

Only to people reading the raw Markdown. Renderers ignore the extra spaces, so untick Pad columns for the shortest output. Without padding, making one cell the longest in its column changes one line in a diff instead of every row.

Other shapes for the same data

For email or a web page use CSV to Table, which copies rich text or HTML. Starting from JSON, there is JSON to Markdown Table, and cells copied out of a spreadsheet go into Excel to HTML Table. To write the rest of the README, open the Markdown editor.

More Data Converters

Browse all data converters

Copied to clipboard!