Why Markdown Tables Break (and How to Fix Them)
Four symptoms, four causes, four fixes — diagnose any misbehaving table in under a minute.
Symptom 1 — The table renders as plain text
Cause: the separator row is missing, misspelled, or separated from the header by a blank line. Fix: place a dash row immediately under the header, with at least three dashes per column, e.g. |:---------|:---:|. Compare against thesyntax reference if it still fails.
Symptom 2 — One cell splits into extra columns
Cause: a literal pipe (|) inside cell content. Fix: escape every in-cell pipe as \|. This is the most common defect in hand-written tables — and the reason converter output escapes pipes automatically.
Symptom 3 — Rows have different column counts
Cause: ragged rows: a dropped empty field, or a quoted CSV value that split early during a manual conversion. Fix: count the header columns, then pad short rows with empty cells (| |) until every row matches. Re-running the source through theExcel converter or CSV converter fixes this class of error by construction.
Symptom 4 — A multiline cell collapses the table
Cause: a literal newline inside a row — renderers end the row there and everything below shifts. Fix: join the cell content onto one line with <br> tags where breaks belong.
The 30-second fix checklist
- Header row present, separator row directly beneath it.
- Every row has the same number of pipes.
- All in-cell pipes escaped as
\|. - No literal newlines inside rows —
<br>instead. - Blank line above the header and below the last row.
Symptom 5 — The table is trapped inside a gray code box
Cause: leading whitespace indentation. In standard Markdown specifications, any block of text preceded by 4 spaces or a single tab character is interpreted as an indented verbatim code block.
Fix: select the entire table in your code editor and press Shift + Tab to remove leading indentation. Ensure the starting pipe of the header row sits flush against column 0 of your document.
Symptom 6 — A blank line splits one table into two disjointed fragments
Cause: inserting an empty row to visually separate categories of data. In GFM parsing logic, an empty line immediately terminates the table state machine. Any data rows placed beneath that blank line lack a delimiter row, causing them to render as ugly unformatted plain text.
Fix: never insert empty lines inside a table. Instead, insert a section header row using bold text or em-dashes across all cells (e.g. | **Category B: Peripheral Hardware** | --- | --- |).
Symptom 7 — MathJax or LaTeX equations breaking column boundaries
Cause: embedding mathematical formulas containing absolute value bars or matrix notation (such as $|x| < 1$ or $\begin{vmatrix} a & b \end{vmatrix}$). Because the Markdown lexer executes before LaTeX rendering engines, the unescaped vertical bars are treated as table delimiters.
Fix: replace raw vertical bars in LaTeX with their corresponding commands: use \lvert x \rvert instead of |x|, or escape the pipe as $\|x\|$.
Automated Diagnostic Script in Node.js
If you maintain documentation with dozens of Markdown tables across large repositories, use this automated validation script in your CI pipeline to catch broken delimiters before code review:
import fs from 'node:fs';
function validateMarkdownTables(content) {
const lines = content.split('\n');
let inTable = false;
let expectedCols = 0;
const errors = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
const isPipeRow = line.startsWith('|') && line.endsWith('|');
if (!inTable && isPipeRow) {
// Potential header
const nextLine = (lines[i + 1] || '').trim();
const isDelimiter = /^\|(?:\s*:?-+:?\s*\|)+$/.test(nextLine);
if (isDelimiter) {
inTable = true;
expectedCols = line.split('|').length - 2;
i++; // skip delimiter row
continue;
}
} else if (inTable) {
if (!isPipeRow) {
inTable = false;
continue;
}
const cells = line.split(/(?<!\\)\|/).length - 2;
if (cells !== expectedCols) {
errors.push('Line ' + (i + 1) + ': Expected ' + expectedCols + ' columns, found ' + cells);
}
}
}
return errors;
}
Running this regex-based token counter flags unescaped pipes and ragged column counts instantly across all your docs.
Symptom 8 — Invisible UTF-8 BOM Header Corrupting Delimiters
Cause: when CSV files are exported from legacy Windows software (like older Microsoft Excel editions), the file may start with an invisible three-byte sequence: 0xEF, 0xBB, 0xBF (the UTF-8 Byte Order Mark). When pasted into a Markdown document, these invisible zero-width bytes sit in front of the opening pipe of line 1, causing GFM parsers to reject the header row.
Fix: in VS Code, inspect the bottom status bar: if it reads UTF-8 with BOM, click it and selectSave with Encoding → UTF-8 (without BOM). Alternatively, re-paste the table through our online converter, which automatically strips zero-width non-breaking spaces and BOM markers during clipboard sanitization.
Symptom 9 — Mixed CRLF vs LF Line Endings in Git Pull Requests
Cause: team members collaborating across Windows and macOS/Linux workstations with misconfigured git core.autocrlf settings. If one developer adds rows with Windows carriage return line feeds (\r\n) to a file using Unix line feeds (\n), older Markdown processors may fail to recognize the row boundaries correctly.
Fix: normalize line endings in your repository by adding a .gitattributes file containing* text=auto eol=lf. Run git add --renormalize . to ensure uniform Unix line endings across all Markdown documentation.
VS Code Regex Search & Replace Formulas
Fix common table formatting errors across hundreds of files in seconds using regular expression find-and-replace:
- Escape Naked Pipes Inside Cells:Find:
(?<=\w)\s*\|\s*(?=\w)→ Replace with:\| - Add Missing Trailing Delimiters:Find:
^(\|.*[^|\s])\s*$→ Replace with:$1 | - Strip Accidental Fenced Code Indentation:Find:
^(?: 4|\t)(\|.*\|)$→ Replace with:$1
Static Site Generator Compatibility: Hugo, Jekyll, & Docusaurus
If your table renders properly on GitHub but breaks inside your documentation portal:
- Hugo Goldmark Engine: In Hugo 0.60+, the default Goldmark parser requires tables extension to be enabled. Verify your
config.tomlincludes:[markup.goldmark.extensions] table = true - Jekyll Kramdown Engine: Kramdown expects an empty line before and after tables. Additionally, Kramdown parses
| --- |without strict leading/trailing pipes, but missing blank lines will cause the table to merge into the preceding paragraph. - Docusaurus / MDX: In MDX environments, curly braces
{ }and angle brackets< >inside table cells are parsed as JavaScript expressions or JSX components. Wrap all code references in backticks to prevent compilation failures.
Emergency Recovery: Re-Converting Damaged Tables in 3 Steps
If a legacy Markdown table has become so mangled by broken column counts and unescaped special characters that hand-editing line-by-line is impractical:
- Select & Copy the Raw Markdown: Copy the broken table text directly from your markdown document into your clipboard.
- Paste into ExcelToMarkdown: Open our converter workbench and paste the text into the input textarea. The lexer will isolate cell contents, strip zero-width BOM noise, and reconstruct a clean rectangular grid.
- Copy Clean Output: Click Copy Markdown to paste the newly repaired, perfectly padded GFM table back into your documentation.
Formatting Tables in Slack, Discord, & Microsoft Teams
Chat applications implement highly restricted Markdown variants that do not support GFM table syntax:
- Slack: Does not render pipe tables. Pasting Markdown tables into Slack results in raw text. To share tabular data in Slack, wrap the formatted table inside a fenced code block (
```table```) to preserve column spacing via monospace alignment. - Discord: Similar to Slack, Discord ignores GFM pipe delimiters. Wrap tables in triple backticks with optional
```asciidocor```markdowncode fences for clean reading. - Microsoft Teams: Rich text mode converts pasted Excel ranges into native HTML cards. If sharing Markdown, use the code snippet dialog.
Related guides
Frequently asked questions
Why does my Markdown table show as plain text with visible pipes?
The separator row is missing or malformed. Add a row of dashes (with optional alignment colons) directly under the header — that row is what triggers table rendering.
Why did one cell split into two columns?
It contains an unescaped pipe character. Escape every literal | inside cell content as \| and the columns will line up again.
Why do some rows have more cells than others?
Ragged rows: an empty field was dropped or a quoted value split early. Pad short rows with empty cells so every row matches the header column count.