What Fits in a Markdown Table Cell — and What Silently Disappears
Type a value into the third column of a two-column Markdown table row. Every engine drops it — no warning, no error, nothing. The cell is gone before the page loads. That silence is the theme of this piece: Markdown tables are forgiving on the surface and quietly destructive underneath. The six questions about table cells on Stack Overflow have collected 1.65 million views combined,1 which suggests the surprises keep catching people.
This post runs one test note through seven renderers — commonmark.js, GitHub's own API, micromark with the GFM extension, markdown-it 15, pandoc gfm, pandoc markdown, and Python-Markdown — and records what each one actually does. The results form the table below. Every claim in the explanations that follow traces back to a row in that table, a spec quote, or a receipt from the test run (2026-10-28).
The measured table
The short answer: a pipe-table cell holds exactly one line of inline text. Everything else listed in this table either works conditionally, fails silently, or requires a dialect outside standard GFM.
| What you put in the cell | commonmark.js | GitHub (POST /markdown) | micromark + GFM | markdown-it 15 | pandoc gfm | pandoc markdown | Python-Markdown tables |
|---|---|---|---|---|---|---|---|
| Any pipe table at all | no table (prints as text) | table | table | table | table | table | table |
line one<br>line two | — | two lines | two lines | two lines | two lines | two lines | two lines |
a | b (escaped pipe) | — | pipe shown | pipe shown | pipe shown | pipe shown | pipe shown | pipe shown |
`a | b` (pipe inside code span) | — | pipe, no backslash | pipe, no backslash | pipe, no backslash | pipe, no backslash | backslash + pipe | backslash + pipe |
- one<br>- two (fake list with dashes) | — | text with dashes | text with dashes | text with dashes | text with dashes | text with dashes | text with dashes |
<ul><li>one</li><li>two</li></ul> | — | a real list | a real list | a real list | a real list | empty cell — content dropped | a real list |
| a row with one cell too many | — | extra cell dropped | dropped | dropped | dropped | dropped | dropped |
| a row with one cell too few | — | empty cell added | added | added | added | added | added |
no header row (| a | b | twice) | — | not a table | not a table | not a table | not a table | not a table | not a table |
empty header row (| | |) | — | table, blank header shown | blank header shown | blank header shown | blank header shown | <thead> omitted | blank header shown |
| merged || | — | no merge: second cell empty | no merge | no merge | no merge | no merge | no merge |
commonmark.js 0.31.2 does not include the GFM tables extension, so it prints every table as literal text.2 That column is a reminder: "works on GitHub" and "works in CommonMark" are not the same sentence. A strict CommonMark renderer treats a pipe table as a paragraph — the pipes print as prose. See Parsing Markdown Edge Cases That Break Editors for the wider pattern.
Line breaks: <br> works, until you export
<br>is the only reliable way to add a second line to a pipe-table cell. It works in all six table engines (GitHub, micromark+GFM, markdown-it withhtml: true, pandocgfm, pandocmarkdown, Python-Markdown). The trouble arrives when you export the note to a format that is not HTML.
Of the six table engines tested, all six render <br> as a line break when HTML output is the target.3 The cell line one<br>line two gives two visually separate lines in a GitHub README, in a rendered blog post, and in any app that targets HTML.
The silent failure happens on export. Run the same table through pandoc targeting LaTeX, plain text, or DOCX and the <br> tag disappears completely — the two lines fuse into line oneline two with no space, no warning, and --fail-if-warnings still exits 0.4 The only indication is the verbose log: [INFO] Not rendering RawInline (Format "html") "<br>" — printed only when you pass --verbose, which almost nobody does. If your workflow ends in a Word document or a PDF, replace <br> with a restructuring decision: split the cell into two rows, or move the content to a section below the table.
The markdown-it default (html: false) and the micromark default print <br> as the escaped visible text <br> rather than a line break.3 Only pass html: true (markdown-it) or allowDangerousHtml (micromark) to enable the break. GitHub's hosted API enables HTML by default.
Pipes: escape with \|, except inside code spans
Escape a pipe in prose with
\|and every engine shows the pipe without the backslash. Inside a code span, the rule inverts: the four GFM engines strip the backslash and show only the pipe, while pandocmarkdownand Python-Markdown keep both characters and print\|literally.
The GFM spec documents the escape rule at §4.10, Example 200: "Include a pipe in a cell's content by escaping it, including inside other inline spans."5 GitHub's own documentation agrees: "To include a pipe \| as content within your cell, use a \ before the pipe."6
The inversion inside code spans is the trap. Write `a \| b` in a table cell and GitHub, micromark+GFM, markdown-it, and pandoc gfm all display a | b — the backslash is consumed as an escape character even inside the code span.7 Pandoc markdown and Python-Markdown treat the contents of a code span as literal characters and display a \| b with the backslash intact. There is no way to write a code span containing a literal backslash-pipe that renders correctly in all six engines simultaneously: the GFM group and the pandoc-markdown group read the escape rule differently inside backticks.
GitHub Docs' Organizing information with tables shows the prose escape; the code-span inversion is undocumented there. If your notes are likely to move between engines, prefer prose descriptions of pipe characters over code spans when the pipe is the point.
For Obsidian users: wikilink aliases use the same pipe — [[file|alias]]. Inside a table cell, escape it as [[file\|alias]]. The same six-engine test confirms the \| fix resolves the cell split.7
Block HTML and lists: pandoc's markdown reader silently deletes them
Block HTML in a pipe-table cell is deleted silently by pandoc's
markdownreader — no warning, no error, and the--fail-if-warningsflag does not help. The other five table engines keep block HTML. Inline HTML (<b>,<em>,<span>) survives in all six.
The pandoc MANUAL states the rule directly, in Extension: pipe_tables: "The cells of pipe tables cannot contain block elements like paragraphs and lists, and cannot span multiple lines."8 Block elements are anything that starts a new block in the HTML model: <ul>, <ol>, <p>, <div>. Inline elements — <b>, <span>, <em>, <br> — are fine.
The silence is the alarming part. Put <ul><li>one</li><li>two</li></ul> in a cell and pandoc markdown produces an empty cell. Its AST (-t native) shows the cell as [] — the content was parsed and discarded, not escaped.9 Export that table to GFM markdown and pandoc writes an empty cell into the source file. There are no warnings at the default log level. Pass --verbose and still nothing about the missing list content. Pass --fail-if-warnings and the command exits 0 — the flag has no effect because pandoc issued no warning. The list is simply gone.
This was confirmed on both pandoc 3.12.1 and 3.6.4, so the behaviour is not a recent regression.
The five other table engines (GitHub, micromark+GFM, markdown-it, pandoc gfm, Python-Markdown) keep the HTML list and render it as real list elements. So a note that looks correct in GitHub's preview or a local Markdown app may silently lose its list content the first time pandoc markdown processes it. The post The Complete Markdown Cheat Sheet covers the broader syntax rules; for the table-specific cell constraint, this is the definitive measurement.
If you need a list in a Markdown document, the practical fix is to move the list content to its own section below the table and add a reference from the cell — see §Details below. The table stays small and the list survives any renderer. For a workflow where pandoc markdown is a required step, this is not optional.
Merged cells: no pipe-table engine does it
No pipe-table engine merges cells.
| merged ||is parsed as a row where the second cell is empty. Colspan and rowspan are absent from every tested pipe-table implementation. Two exits exist: pandoc's grid table extension (for HTML/DOCX export) and the MultiMarkdown||syntax (plugin only).
The GFM spec §4.10 states: "If there are a number of cells fewer than the number of cells in the header row, empty cells are inserted. If there are greater, the excess is ignored."10 There is no merge provision. The six pipe-table engines tested all treat | merged || as a row with an occupied first cell and an empty second cell — no engine produces colspan="2".7
Two genuine exits exist, and both operate outside the pipe-table format:
Pandoc grid tables support full block content and cell spanning. The pandoc MANUAL describes it: "The cells of grid tables may contain arbitrary block elements (multiple paragraphs, code blocks, lists, etc.)."11 Grid tables also support colspan and rowspan. The trade-off is that GitHub renders a pandoc grid table as literal text — the ASCII art is not parsed. Grid tables are the right tool when your output target is DOCX, LaTeX, or a pandoc-native HTML pipeline, not when the source file needs to render on GitHub.
MultiMarkdown documents || as a column-span marker (MultiMarkdown 6). In all six pipe-table engines tested, || produces an empty cell. The MultiMarkdown behaviour requires the MultiMarkdown parser or a dedicated plugin (e.g. markdown-it-multimd-table). It is not part of GFM or CommonMark.
If your note needs merged cells, the table is probably too complex for Markdown. Consider a section-and-list structure, a CSV for the data, or an HTML <table> block (which every browser and most converters handle, though pandoc markdown will delete it from a pipe-table cell for the reasons above).
The header row is not optional
Every pipe-table engine requires a header row followed by a delimiter row (at least three hyphens per cell). A note with two identical rows of pipes — no delimiter — is not a table; every engine prints it as text. An empty header row is allowed, but pandoc
markdownand pandocgfmsilently omit the<thead>element.
The GFM spec §4.10 is definitive: "The header row must match the delimiter row in the number of cells. If not, a table will not be recognized."10 Pandoc echoes this: "The header cannot be omitted."8
All six table engines reject a Markdown table with no delimiter row.12 If you want a headerless table in your rendered output, the available options are:
- Use an empty header row (
| | |) — the header renders as blank cells. Both pandoc readers omit<thead>, making the empty row invisible in HTML; the other four engines show a blank first row. - Use pandoc grid tables with an empty header line.
- Use raw HTML
<table>— but see the silent-delete risk for pandocmarkdownabove.
The column-count mismatch rule has one engine-specific exception: pandoc markdown builds a table even when the header row and delimiter row have different column counts (it uses the wider count to set the table structure). Every other engine refuses.12
The decision rule
Three rules cover every practical case:
- Keep cells to one line of inline text. Inline elements — bold, italic, code, links — work everywhere. Anything longer than one line is a candidate for a section, not a cell.
- Use
<br>for a second line when HTML is your only output target. Replace it with restructuring if the note ever reaches pandoc targeting LaTeX, plain text, or DOCX. - When a cell needs a list, a paragraph, or a merged row, the table format is too small. Either move the content to a heading-and-list section below the table, or exit to pandoc grid tables if your pipeline handles them. For data-heavy notes that outgrow their table, Tables and CSV in Plain Text You Own covers the CSV exit.
The pipe table format was designed for one thing: small, inline reference grids. It handles that case well. The silence of its failures — the vanished list, the fused export line, the dropped extra cell — is what makes it dangerous to push past that boundary without knowing where it breaks.
FAQ
How do I add a line break in a Markdown table cell?
Use <br> — for example, first line<br>second line. This works in all six pipe-table engines when the output target is HTML. If you export to LaTeX, DOCX, or plain text with pandoc, the <br> is silently deleted and the two lines fuse. Restructure the cell into two rows instead.
Can I merge cells in a Markdown table?
Not in any pipe-table engine. | merged || produces an empty second cell in all six tested implementations. Pandoc's grid table extension supports colspan and rowspan, but GitHub renders grid tables as literal text. MultiMarkdown documents || as a merge marker, but it requires the MultiMarkdown parser or a plugin; it is an empty cell in all standard pipe-table engines.
How do I put a list inside a Markdown table cell?
Only with HTML: <ul><li>one</li><li>two</li></ul>. Five of the six tested engines render the HTML list. The exception is pandoc markdown, which deletes block HTML from a pipe-table cell completely — the cell is empty and no warning is issued. For notes that pass through pandoc, move the list to a section below the table and reference it from the cell.
Can I create a Markdown table without a header row?
No. Every pipe-table engine requires a header row followed by a delimiter row (at least three hyphens per column). A second content row without a delimiter produces text, not a table. You can use an empty header row (| | |) to suppress visible header content; pandoc's two readers omit the <thead> element for this case.
How do I escape a pipe character inside a Markdown table cell?
Use \| in prose. In a code span, the behaviour differs by engine: GitHub, micromark+GFM, markdown-it, and pandoc gfm strip the backslash and display only the pipe, while pandoc markdown and Python-Markdown keep the backslash as a literal character. There is no single syntax that renders identically across both groups.
Does GitHub support colspan in Markdown tables?
No. GitHub's Markdown parser (GFM) has no colspan or rowspan support for pipe tables. | merged || is an empty second cell. Pandoc grid tables have real span support but GitHub renders them as ASCII text, not as an HTML table.
Why does my Markdown table look fine on GitHub but lose content in another app?
The most common cause is block HTML in a cell. Pandoc's markdown reader deletes block elements — <ul>, <p>, <div> — from pipe-table cells silently. GitHub displays the HTML; pandoc makes the cell empty. The second common cause is <br> in a cell exported to LaTeX or DOCX: pandoc deletes the break and fuses the text. Check the measured table above for the full breakdown by engine.
References
The pipe table is one of Markdown's most useful one-liners — and one of its most reliable quiet failures. Know what a cell can hold, test the renderer you actually ship to, and keep the table small enough that a cell never needs to be a page.
Markdown notes that stay readable across every tool — mnmnote.com.
Footnotes
-
Stack Overflow API. Six questions: "How do I add a newline in a markdown table?" (q/11700487, 399,269 views); "Can I merge table rows in markdown" (q/46621765, 343,554 views); "How to write lists inside a markdown table?" (q/19950648, 313,194 views); "Create a table without a header in Markdown" (q/17536216, 301,841 views); "GitHub Markdown colspan" (q/23571724, 227,167 views); "How to escape a pipe char in a code statement in a markdown table?" (q/17319940, 63,229 views). API pull 2026-10-28. https://api.stackexchange.com/2.3/questions/11700487;46621765;19950648;17536216;23571724;17319940 ↩
-
Own measurement. commonmark.js 0.31.2 (no GFM tables extension): pipe tables print as literal text; 6 of 6 table engines render the table. Receipts/tally.txt, 2026-10-28. ↩
-
Own measurement.
<br>renders as a line break in 6 of 6 table engines when targeting HTML. markdown-it 15 default (html: false) and micromark default print<br>as escaped visible text; both render the break with HTML enabled. Receipts/tally.txt case B, 2026-10-28. ↩ ↩2 -
Own measurement. pandoc 3.12.1 (also confirmed 3.6.4):
<br>in a table cell targeting LaTeX/plain/DOCX → break deleted, words fused;--fail-if-warningsexits 0;--verboseprints[INFO] Not rendering RawInline (Format "html") "<br>". Receipts/br-export-out.txt, 2026-10-28. ↩ -
GitHub. "GFM Tables Extension — Example 200." GFM Spec 0.29-gfm §4.10. https://github.github.com/gfm/#tables-extension- ↩
-
GitHub Docs. "Organizing information with tables." https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables — accessed 2026-10-28. ↩
-
Own measurement. Pipe inside code span: 4 GFM engines strip backslash; pandoc
markdown+ Python-Markdown keep it. Extra cell dropped 6/6; too few padded 6/6;||no merge 6/6; wikilink alias\|fix 6/6. Receipts cases D/E/H/I/N/P, 2026-10-28. ↩ ↩2 ↩3 -
John MacFarlane. pandoc User's Guide, Extension: pipe_tables. "The cells of pipe tables cannot contain block elements like paragraphs and lists, and cannot span multiple lines." "The header cannot be omitted." Pandoc 3.12.1 (living doc, fetched 2026-10-28). https://pandoc.org/MANUAL.html#extension-pipe_tables ↩ ↩2
-
Own measurement. pandoc 3.12.1 and 3.6.4:
<ul><li>one</li><li>two</li></ul>in a pipe-table cell → AST[], empty cell on md→gfm export, 0 warnings (--verbose,--fail-if-warningsexit 0). Receipts cases G/Q/R + pandoc-warnings.txt, 2026-10-28. ↩ -
GitHub. "GFM Tables Extension." GFM Spec 0.29-gfm §4.10. "The header row must match the delimiter row in the number of cells. If not, a table will not be recognized." "If there are greater, the excess is ignored." https://github.github.com/gfm/#tables-extension- ↩ ↩2
-
John MacFarlane. pandoc User's Guide, Extension: grid_tables. "The cells of grid tables may contain arbitrary block elements (multiple paragraphs, code blocks, lists, etc.)." https://pandoc.org/MANUAL.html#extension-grid_tables ↩
-
Own measurement. No header → not a table 6/6; empty header → pandoc ×2 omit
<thead>; delimiter-count mismatch → pandocmarkdownbuilds table; 5 of 6 refuse. Receipts cases J/K/O, 2026-10-28. ↩ ↩2