Markdown Tables: The Syntax That Works, and the Three Rules That Break It
· 6 min read
How Markdown tables actually work, the four rules that break them silently, and how to check whether a renderer supports the GFM table extension at all.

A Markdown table is four lines of syntax and about a dozen ways to get it subtly wrong. Miss the delimiter row and the whole thing collapses into a paragraph. Put a cell across two source lines and half the renderers disagree with the other half. Leave a pipe unescaped inside a code span and the column count silently shifts.
None of these produce an error. The document still renders, which is the real problem, because a broken table looks like a styling problem and gets missed. The syntax is small enough to learn completely, and the failure modes are few enough to check by eye once you know them.
Key Takeaways
- A table needs a header row, a delimiter row of dashes, and at least one body row.
- Column count is set by the header row. Too few pipes and the rest of the row is dropped.
- Keep every cell on one line. Wrapping a row across source lines renders inconsistently.
- Alignment is set with colons in the delimiter row, not with spaces.
What Are Markdown Tables?
Tables are a GitHub Flavored Markdown extension rather than part of the CommonMark specification. That distinction matters more than it sounds: a strict CommonMark parser has no concept of a table, so a pipe-delimited block renders as an ordinary paragraph with the pipes visible.
GFM defines the syntax as a header row, a delimiter row that also carries alignment, and any number of body rows. The GitHub Flavored Markdown specification states it as an extension to the base grammar, and the GitHub documentation shows the rendered result.
| Syntax | Produces |
|---|---|
| Pipe-delimited rows | A table |
| inside a cell |
A literal pipe character |
`code | span` |
A pipe inside a code span, literal |
The CommonMark specification, which has no table concept at all, is the baseline this extends. The base syntax is three lines:
| Tool | Runs locally |
| --- | --- |
| Converter | Yes |
The leading and trailing pipes are optional. Tool | Runs locally works identically, which is worth knowing because it means the pipes you leave off are the ones that matter.
The rules that follow are the ones that actually cause problems, and all of them share a property: none of them raise an error. See the GitHub Flavored Markdown tables extension for the formal grammar. First, the delimiter row.
The middle row is what makes the block a table rather than a paragraph. It consists of pipes and hyphens, and it does double duty: it marks the end of the header and it carries the column count.
This is the first rule people miss, because a table without the delimiter row looks almost right in the source:
| Tool | Runs locally |
| Converter | Yes |
That is not a table with one row. It is two lines of paragraph text, and it renders as two lines of paragraph text, pipes included. The failure is invisible in a preview that is scrolled past quickly.
Column count comes from the header
Once the delimiter row is present, the number of columns is fixed by the header row. A body row with fewer cells than the header has the extras dropped. A body row with more cells has the overflow truncated.
| Tool | Runs locally |
| --- | --- |
| Converter | Yes | Extra |
Here the third cell vanishes without warning. The reverse case, a body row with too few cells, leaves a gap. Neither is an error in most implementations, because the GFM spec defines the behavior rather than forbidding the input. The Markdown syntax cheat sheet covers the surrounding CommonMark constructs this builds on.
The practical rule is to count pipes. Every row in a table should have the same number of them, and a quick scan down the block catches a mismatch faster than reading the rendered output.
Tables are a GitHub Flavored Markdown extension, and the GitHub docs show the rendered result. Every rule below shares one property: none of them raise an error.
Keep cells on one line
The most common real-world failure is wrapping a long cell across two source lines for readability:
| Tool | Runs locally |
| --- | --- |
| Converter |
Yes |
This does not wrap. It creates two body rows: one with a single cell and one with a single cell, both of which render as short rows in a table expecting two columns. Wrapping a cell in a source file is a reasonable thing to want, and Markdown simply does not support it.
The workaround is to keep the cell long and let the renderer handle display, or to shorten the content. If a cell is too long for one line, that is usually a sign the table wants to be paragraphs instead.
Pipes inside cells
A literal pipe character inside a cell has to be escaped, because an unescaped one reads as a column boundary:
| Operator | Meaning |
| --- | --- |
| `a \| b` | Logical OR |
The backslash escape works inside a code span, which is what makes this usable in a table of operators or syntax, following the same escaping rule the CommonMark backslash escape defines elsewhere. Without it, the cell splits and the column count for that row goes wrong.
The same applies to a pipe inside a link title or an image path. If a cell genuinely needs a raw pipe and cannot be escaped, the alternative is the HTML entity |, which renderers resolve to the same character without being parsed as a delimiter.
Alignment
Alignment is set in the delimiter row using colons, and it applies to the whole column:
| Left | Center | Right |
| :--- | :----: | ----: |
| text | text | text |
A single leading colon means left-aligned, colons on both sides means centered, and a single trailing colon means right-aligned. No colon means the renderer's default, which is usually left.
The GitHub documentation on Markdown shows this syntax with its rendered result. Alignment is the one genuinely presentational feature in the table syntax, and it is worth using sparingly. A table where every column has a different alignment is harder to scan than one that is uniformly left-aligned, because the eye loses its anchor.
Tables in a converter
A converter needs two things to render tables correctly. The parser must be configured for GFM, since the construct does not exist in base CommonMark. And the output must be sanitized, because a table cell can carry the same raw HTML and attributes as any other part of a document.
Both are handled by the same configuration in most implementations. The Markdown to HTML converter parses with GFM enabled and sanitizes the result, so a table with an inline image or a code span in a cell renders as real markup rather than literal text.
To check whether a converter handles tables, paste a three-line table into it and look at the output. If you see <table> with <th> cells, the GFM extension is active. If you see the pipes, it is parsing CommonMark only, and no amount of correct syntax on your side will change that.
Related tools and further reading
The Markdown syntax cheat sheet covers the core CommonMark constructs and marks the table syntax as a GFM addition rather than a base one. The HTML Table Generator from CSV builds the HTML directly when you already have tabular data and do not want to hand-write the pipes. The Markdown to HTML converter previews a table as you type it. For the dialect boundary that decides whether tables render at all, CommonMark vs GitHub Flavored Markdown covers which side of the line a renderer falls on.
Written by
Abhay Khant
Abhay Khant is the founder of ToolSura, a privacy-first developer tools platform. Writes about client-side architecture, AI tooling, and the open web.