Markdown tables come from GitHub Flavored Markdown (GFM), not from CommonMark: a plain CommonMark parser shows the same lines as a paragraph full of pipes. The rules are short, in section 4.10 of the GFM spec, but a few of them decide whether your table renders at all. This guide goes through those rules with outputs recorded on 2026-10-02 from the GitHub and Gitee Markdown APIs, Qiita’s renderer (qiita_marker 0.23.9.0), Zenn’s renderer (zenn-markdown-html 0.5.4) and micromark, the parser behind remark, MDX and Astro.
The three parts of a table
| Name | Role |
| ----- | -------- |
| Ana | Backend |
| Ben | Frontend |
- The header row: the first line, cells separated by
|. - The delimiter row: one cell of hyphens per header cell, optionally with colons.
- The body rows: everything after, up to the end of the table.
Pipes at the start and end of a line are optional, so Name | Role on the first line and -- | -- on the second also make a table. With one column, though, at least one of the two rows needs a pipe; a followed by --- is a heading. The GFM spec accepts a single hyphen per delimiter cell and so does GitLab (“Each cell must contain at least one hyphen”), but the Obsidian help says the header row needs at least two. Three hyphens work everywhere.
Alignment with colons
The colons in the delimiter row set the alignment of the whole column:
| Delimiter cell | Alignment | HTML |
|---|---|---|
--- | none (the browser default, usually left) | no align attribute |
:--- | left | align="left" |
:---: | center | align="center" |
---: | right | align="right" |
--- and :--- look the same in most themes, but only the second one is an explicit choice. Header cells follow the column alignment in GitHub’s HTML; GitLab’s documentation notes that its table headers are always left-aligned in Chrome and Firefox and centered in Safari. Right-align columns of numbers so the digits line up.
Where a table starts and ends
Three rules from the spec explain most broken tables:
- The header and the delimiter row must have the same number of cells. If they do not, there is no table.
| a | b |over|---|renders as a paragraph on GitHub, Gitee, Qiita, Zenn and micromark alike. - Body rows may have fewer or more cells. Missing cells are added empty; extra cells are dropped without a warning. A row such as
| Ben | QA | extra |under a two-column header losesextraon every renderer above. - The table ends at a blank line or at the start of another block (a heading, a list, a block quote, a fence, a thematic break). A plain line of text right after the last row is not the end: it becomes another row.
The last point surprises people. Here is a document with no blank lines around the table:
# Doc
Intro line.
| Name | Role |
| --- | --- |
| Ana | Dev |
| Ben | QA | extra |
| Cy |
Next paragraph.
GitHub, Gitee, Qiita, Zenn and micromark all start the table right after Intro line. (a table may interrupt a paragraph), drop extra, give Cy’s row an empty second cell and turn Next paragraph. into a fourth row. markdownlint 0.40.0 with its default rules reports this (line number and rule; messages shortened):
4 MD058/blanks-around-tables
7 MD056/table-column-count Expected: 2; Actual: 3; Too many cells, extra data will be missing
8 MD056/table-column-count Expected: 2; Actual: 1; Too few cells, row will be missing data
9 MD055/table-pipe-style Missing leading pipe / Missing trailing pipe
9 MD056/table-column-count Expected: 2; Actual: 1; Too few cells, row will be missing data
Put a blank line before and after every table.
Pipes, backslashes and code spans
A | inside a cell must be written \|. This applies inside code spans too, which is where most people get caught: the table rows are split at pipes before the cell content is parsed, so `a|b` ends the cell after `a. Written as `a\|b`, all of the renderers above show the code a|b; the backslash is removed. To put a regular expression like (png|jpg) in a table you write (png\|jpg).
A backslash right before the pipe is where renderers disagree. For the cell text x\\|y:
GitHub, Gitee, Qiita, Zenn one cell, shown as x|y
micromark (remark, MDX, Astro) two cells: x\ and y
GitHub and markdown-it (which Zenn uses) treat any pipe after a backslash as escaped. micromark reads \\ first as an escaped backslash, so the pipe after it splits the cell. If you need a literal backslash followed by a pipe, write three backslashes: \\\| shows \| in all five. The Markdown table generator writes cells this way, and its test compares every renderer’s output cell by cell.
What a cell can hold
Cells contain inline Markdown only: emphasis, code, links, images, autolinks. A cell is one line, so a list, a code block or a second paragraph cannot go in it. What you can do:
- Line breaks with
<br>. GitLab’s documentation shows exactly this (“You can also include HTML<br>tags to force newlines”), and GitHub, Gitee, Qiita and Zenn render it. A site that strips raw HTML shows the tag as text, so check before relying on it. - Pseudo lists with
<br>- item<br>- item, which is the GitLab documentation’s own example. - Task boxes on GitLab 18.9 and later, as the only content of a cell (
[x],[ ]). - Real block content with an HTML
<table>instead of Markdown; GitLab documents leaving blank lines around Markdown inside<td>. - Wiki links with aliases in Obsidian,
[[Note\|alias]]: the Obsidian help says to escape that pipe too.
Leading and trailing spaces in a cell are trimmed, so you cannot indent a cell’s content with spaces; is the usual workaround.
There is no colspan or rowspan in GFM. When a spreadsheet range has merged cells, the generator splits them and puts the text in the first cell.
CJK text in tables
Two things go wrong with Chinese, Japanese and Korean text.
Bold next to full-width punctuation. **注意:**ここ is not bold on any of the renderers above. This is a CommonMark rule, not a table rule: a closing ** must not sit between punctuation and a letter. **注意**:ここ (the colon outside) works.
Aligning the source. Tables do not need aligned pipes to render, but aligned sources are easier to review. Alignment needs display width, not character count: CJK characters and emoji take two columns in a monospace font (Unicode UAX #11). Padding by character count, as tablesgenerator.com, tableconvert.com and tabletomarkdown.com did when tested on 2026-10-02, leaves 東京 two columns too wide. The generator pads by display width, using the same rules as the string-width package that markdownlint’s MD060 rule uses:
| Office | Name | Staff |
| ----------- | --------- | ----: |
| Head office | 本社 | 120 |
| Busan | 부산 지점 | 8 |
Symbols such as ○, × and ① are “ambiguous” in UAX #11: one column in Western fonts, two in most East Asian ones. The generator has an option to count them as two.
Turning spreadsheet data into a table
Copying a range from Excel or Google Sheets and pasting it into the generator’s grid gives you a GFM table without retyping. The details that break hand-made conversions are handled: an empty top-left cell keeps its column, a cell with a line break gets <br>, and a | in the data is escaped. With the first column right-aligned:
| Region | Q1 | Note |
| ------ | ----: | -------------------- |
| North | 1,200 | Delayed:<br>supplier |
| South | 980 | a\|b |
If the data contains * or _ that should stay literal, switch Cell text to Plain text and the generator escapes them (\*, \_), leaving underscores inside words alone.
Checklist before you commit a table
- Same number of cells in the header and the delimiter row.
- A blank line before and after the table.
- Every
|inside a cell written\|, including inside code. <br>for line breaks, and only if your site keeps that HTML.- Bold markers outside full-width punctuation in CJK text.
- Three hyphens per delimiter cell if the file is also opened in Obsidian.
- Run markdownlint (MD055, MD056, MD058, MD060) in CI if tables change often.