Markdown tables: syntax, alignment, and limits
Bobby Huang ·
A Markdown table is a header row, a delimiter row of hyphens under it, and any number of data rows, with cells separated by pipes. Colons in the delimiter row set alignment. The header and delimiter rows must have the same number of cells, a literal pipe in a cell needs a backslash, and the table ends at the first blank line. Tables are a GFM extension, so tools without GFM table support show the raw pipes. Cells hold inline content only: no lists, no merged cells, and each row is one line of the file.
Key takeaways
- A Markdown table needs a header row, a delimiter row of hyphens, and zero or more data rows.
- Colons in the delimiter row set alignment: left, right or center.
- If the header and delimiter rows have different cell counts, the table is not recognized at all.
- Cells hold inline content only, one line each, with no merged cells, so long or structured text belongs in a list under the table.
- Sashmark is in development and not released; rendering tables and editing cells in place are planned for v1.
A Markdown table is a simple way to put a status grid or a side-by-side comparison into a plain text file. AI agents write them too: a task table at the top of a plan, a comparison of three options, a list of files and what changed in each.
They also break easily. One missing pipe in the wrong row and the whole thing turns back into a line of dashes and bars.
Tables are a GFM extension, not part of core CommonMark. If you want the rest of the syntax in one place, the Markdown syntax guide covers it. Here it's tables only.
How to make a Markdown table
A table has three parts: one header row, a delimiter row under it, and zero or more data rows.
- Write the header row. Put each column name between pipes:
| Task | Owner | Status |. - Write the delimiter row. Under it, put one cell of hyphens per column:
| --- | --- | --- |. Three hyphens is the usual habit. - Write the data rows. One row per line, with the same pipe pattern.
- Leave a blank line after the table before the next paragraph. A blank line before it is not required, but it keeps the raw file easy to read.
| Task | Owner | Status |
| ----------- | ----- | ------ |
| Draft plan | agent | done |
| Review plan | you | open |
| Merge | you | open |
That renders as a grid with three columns headed Task, Owner and Status, a rule under the header, and three data rows below it. The pipes and hyphens disappear. You see only the cell text, laid out in columns.
Align columns with colons
The delimiter row does a second job: it sets alignment. Its cells hold only hyphens, plus an optional leading colon, trailing colon, or both.
:---aligns the column left.---:aligns it right.:---:centers it.
| File | Lines | Status |
| :-------- | ----: | :-----: |
| plan.md | 120 | changed |
| notes.md | 8 | new |
Here the File column is left aligned, Lines is right aligned (handy for numbers), and Status is centered. A column with no colon gets the renderer's default.
Rules that trip people up
Every table needs a delimiter row
A header row on its own is just a paragraph with pipes in it. Without the hyphen row under it, nothing renders as a table.
| Task | Status |
| Draft | done |
That's two lines of text, not a table. Add | --- | --- | after the first line.
Leading and trailing pipes are optional
Cells are separated by pipes. Pipes at the start and end of each row are recommended because they make rows easier to read, but they can be left off, and rows don't have to agree with each other.
Task | Status
--- | ---
Draft | done
| Review | open |
That still renders as a two-column table.
Columns don't need to line up
Spaces between the pipes and the cell text are trimmed. These two tables render exactly the same:
| Task | Status |
| --- | --- |
| Draft | done |
| Task | Status |
| ----- | ------ |
| Draft | done |
The padded one is easier to read in the raw file. That's its only benefit.
Header and delimiter counts must match
This rule is easy to miss. If the header row has three cells and the delimiter row has two, the table is not recognized at all.
| Task | Owner | Status |
| --- | --- |
| Draft | agent | done |
That shows as raw text. Add the missing --- | to the delimiter row and it renders.
Data rows are forgiving
Data rows don't have to match. A short row gets empty cells added at the end. A long row has its extra cells ignored.
| Task | Status |
| --- | --- |
| Draft |
| Review | open | extra |
The Draft row renders with an empty Status cell. The Review row renders with "open" and "extra" disappears. That last part is the trap: no error, the text just isn't there.
A pipe in a cell needs a backslash
A bare pipe inside a cell starts a new cell. Escape it as \|. That goes for pipes inside inline code in a cell too.
| Command | Meaning |
| --- | --- |
| `a \| b` | send a into b |
The table ends at a blank line
The table stops at the first blank line, or when another block starts, such as a heading or a list. A stray blank line in the middle splits one table into a table plus a paragraph of pipes.
| Task | Status |
| --- | --- |
| Draft | done |
| Review | open |
The last row is now a separate line of text, not part of the table.
What tables can't do
GFM tables are simple. Three limits to know:
- Inline content only. Cells can hold emphasis, links and inline code. They can't hold block content: no lists, headings, block quotes or fenced code.
- One line per row. Each row is one line of the file. There is no way in GFM to write one cell across several lines.
- No merged cells and no caption. There's no column span, no row span, and no caption row.
You can work around all three and stay in plain Markdown:
- Split a big table. If one table is trying to cover two topics, make it two tables, each under its own heading.
- Move long text into a list under the table. Keep the cell to a few words, then add the detail as a list below.
- Use short cell text plus a note. Put a marker in the cell, such as "see note 1", and write the note under the table.
| Option | Effort | Risk |
| --- | --- | --- |
| A | low | see note 1 |
| B | high | low |
Note 1: risk depends on whether the old config is still read on startup.
GFM allows inline HTML in a cell, so a <br> tag can force a rendered line break. Some tools show the tag as text or drop it, though, so don't count on it in a file that more than one tool will open.
If you want a checkbox in a cell, what you may really have is a to-do list, and Markdown task lists can work better than a Status column.
Fixing a broken table
When a table shows as raw pipes, work through this list in order:
- Count the cells in the header row and the delimiter row. They must match.
- Check for a stray blank line in the middle of the table. It ends the table there.
- Escape pipes inside cells, including pipes inside inline code.
- Check the delimiter cells hold only hyphens and colons, nothing else.
- Check the tool supports GFM tables. If it doesn't, every table shows as raw text no matter how correct it is.
If the table renders but text is missing, look for data rows with more cells than the header. The extra cells were dropped without a warning.
Agent-written tables and noisy diffs
An agent-written table may come padded, with every pipe lined up. That's fine until someone edits one cell. If the new text is longer than the column, a tidy-minded agent or editor re-pads the whole column, and every row in the table changes. Your one-word fix shows up in the diff as a rewrite of the table.
Two habits help:
- Ask your agent not to re-pad columns when it changes one cell. Ragged columns render exactly the same.
- Check what your own editor does on save. Some reformat tables you didn't touch, which the post on why editors reformat Markdown on save explains.
There's more on this in keeping agent Markdown diffs small.
Where Sashmark fits
Sashmark is a Markdown reader and editor for people whose AI agents write Markdown, being built for Mac, Windows and Linux. It is in development and has not been released, and the app has no AI features of its own.
Tables are a good test for any reader. A good reader should render tables cleanly and let you fix a cell on the formatted page without reformatting the rest of the file. Sashmark is being built for this; the roadmap shows where it stands. Rendering tables and editing table cells in place are planned for v1, and the edit should change only the cell you touched, not the padding on every row. See the planned features for the full list.
Sashmark is in development. See what it is at sashmark.com.
Questions
- Why does my Markdown table show up as plain text with pipes?
- Either the tool doesn't support GFM tables, or the table breaks a rule. Check that the header row and the delimiter row have the same number of cells, and that no blank line splits the table in the middle.
- Do I have to line up the pipes in a Markdown table?
- No. Spaces around cell text are trimmed, and the columns don't need to line up. Aligned columns only make the raw file easier to read. They also make diffs larger, because changing one cell can change the padding on every row.
- Can I put a list or a line break inside a table cell?
- Not a list. Cells hold inline content only, and each row is one line of the file. GFM allows inline HTML such as a br tag for a rendered line break, but some tools show the tag as text or ignore it, so don't count on it. For longer text, keep the cell short and put a list under the table.