Markdown table syntax: how to make a table in Markdown
Markdown table syntax with copyable examples: pipes, the separator row, alignment, escaped pipes, line breaks in cells, and where tables render.
A Markdown table is a header row, a separator row of hyphens, then one line per row, with a pipe character (|) between cells. Copy this and change the words:
| Task | Owner | Due |
| :-------------- | :---: | ---------: |
| Draft the brief | Priya | 2026-10-02 |
| Book the room | Sam | 2026-10-05 |
Colons in the separator row set alignment: :--- aligns a column left, ---: aligns it right, and :---: centers it. Leave a blank line above the table and give the separator row one cell per header cell. The table then renders on GitHub and GitLab, in notes apps such as Jotura and Obsidian, in VS Code and in the static site generators listed below.
The rules of the table syntax
These rules come from the GitHub Flavored Markdown (GFM) spec, which GitHub, GitLab and the renderers listed below follow.
- The header row is required. GFM has no header-less table.
- The separator row needs one cell per header cell. Each cell holds hyphens, with an optional colon at either end. Three header cells over two separator cells render as plain text.
- Body rows can be ragged. A short row gets empty cells at the end. A long row loses its extra cells.
- The outer pipes are optional.
Name | Roleworks as a row, though outer pipes make the source easier to scan. - The pipes do not need to line up.
|-|-|renders the same as a padded separator. - A blank line ends the table, as does the start of a heading, list or other block.
GitHub’s documentation asks for three hyphens per separator cell and a blank line before the table. In tests on October 10, 2026, GitHub accepted one hyphen and a table directly under a paragraph. Python-Markdown, the parser behind MkDocs, rejected a table with no blank line above it. Three hyphens and a blank line keep a table safe in every GFM renderer.
Why your table shows as plain text
Work down this list:
- No blank line above the table. Python-Markdown and some other parsers need one.
- The separator row has a different number of cells than the header row.
- The separator row holds something other than hyphens, colons, pipes and spaces.
- The table sits inside an indented block, which turns it into a code block.
- The renderer implements plain CommonMark with no table extension.
The last cause is common in minimal tools. Tables were never part of John Gruber’s original Markdown or of CommonMark, the standard behind markdown-it and commonmark.js (version 0.31.2 at the time of writing). GitHub added them as an extension, and the renderers in the next table follow its version.
Where Markdown tables render
| Where | Pipe tables | Raw HTML such as <br> |
Worth knowing |
|---|---|---|---|
| GitHub | Yes | Yes, sanitized | Wide tables scroll sideways |
| GitLab | Yes | Yes, sanitized | Also offers JSON tables in a json:table code block |
| Jotura | Yes, edited as a table | Only <br> in a cell |
Type /table to insert one |
| Obsidian | Yes | Yes, sanitized | Escape pipes in wikilink aliases |
| VS Code preview | Yes | Yes | Built on markdown-it |
| Jupyter Notebook | Yes, in Markdown cells | Yes | Uses GFM |
| Hugo | Yes, on by default | Stripped unless unsafe = true |
Uses the Goldmark parser |
| Astro | Yes, GFM on by default | Yes | Turn off with gfm: false |
| Docusaurus | Yes, through remark-gfm | Write <br /> |
Files compile as MDX |
| MkDocs | Yes, tables on by default |
Yes | Needs the blank line above |
| Eleventy | Yes, through markdown-it | Yes | Sets html: true by default |
| Slack | No | No | Message formatting has no tables |
| Confluence wiki markup | Own syntax | No | ||Header|| for header cells, |cell| for the rest |
| CommonMark-only parsers | No | Varies | Pipes show as one paragraph of text |
The reference commonmark.js library is an example of the last row: it renders the sample table above as a single paragraph.
Alignment details
Alignment applies to the whole column, and no syntax aligns a single cell. Right-align columns of numbers so the digits line up.
With no colon, GFM writes no alignment at all and the stylesheet decides. Body cells then sit left. Header cells often sit centered, because browsers center <th> by default and GitHub’s stylesheet leaves that alone. Use :--- to force a column left, header included.
| Item | Qty | Price |
| :------- | :-: | -----: |
| Notebook | 2 | $4.50 |
| Pens | 10 | $12.00 |
Formatting, links and code inside cells
Cells accept inline Markdown: bold, italic, strikethrough, inline code, links and images.
| Command | What it does |
| ---------- | ------------------------------------------------------------- |
| `git add` | Stages changes. See the [docs](https://git-scm.com/docs/git-add). |
| `git push` | **Uploads** commits to the remote. |
Block elements cannot go in a cell. Headings, lists, block quotes, fenced code blocks and horizontal rules break out of the table or show as literal text.
Escaping a pipe inside a cell
A bare | inside a cell starts a new cell. Write \| for a literal pipe. Under the GFM spec this applies everywhere in the cell, inline code included:
| Operator | Meaning |
| -------- | ----------- |
| `a \| b` | bitwise OR |
| `a && b` | logical AND |
GitHub, the VS Code preview and the marked library show a | b in that first row. Python-Markdown keeps the backslash visible inside backticks and accepts a bare pipe there, so MkDocs sites write `a | b` unescaped. The HTML entity | works in plain cell text and shows up literally inside code.
Obsidian applies the same escape to wikilinks with an alias and to resized images. Inside a table, write [[Project plan\|plan]] and ![[diagram.png\|300]].
Line breaks inside a cell
A GFM row must sit on one line of source. Break a line inside a cell with the HTML tag <br>:
| Step | Notes |
| ------- | ------------------------------------- |
| Install | Download the installer<br>Run it once |
| Sign in | Use your work email |
GitHub, GitLab, Jotura, Obsidian and the VS Code preview render it. Three renderers need different handling:
- MDX, which covers Docusaurus
.mdfiles by default, rejects<br>with “Expected a closing tag”. Write<br />. - Hugo strips raw HTML until you set
markup.goldmark.renderer.unsafe = true. - Renderers with HTML switched off show
<br>as visible text.
What GFM tables cannot do, and the workarounds
| You want | Workaround |
|---|---|
| Merged cells (colspan, rowspan) | An HTML table, or two separate tables |
| A list or paragraphs in a cell | <br>, HTML inside the cell, or a Pandoc grid table |
| A table with no header row | An empty header row |
| Column widths or colors | CSS on your site, or your editor’s own setting |
Merged cells
Write the table in HTML. GitHub, Obsidian and any static site generator that allows raw HTML render it, and GitHub keeps colspan and rowspan:
<table>
<tr><th>Team</th><th colspan="2">Hours</th></tr>
<tr><td>Design</td><td>Mon 4</td><td>Tue 6</td></tr>
<tr><td>Support</td><td>Mon 8</td><td>Tue 2</td></tr>
</table>
Obsidian does not render Markdown inside HTML elements, so formatting in those cells has to be HTML too.
Lists and multi-line content in a cell
GitHub renders HTML list tags inside an ordinary pipe table:
| Step | Details |
| ------- | ------------------------------------------------------------- |
| Install | <ul><li>Download the file</li><li>Run the installer</li></ul> |
Pandoc grid tables allow real block content in cells:
+-----------+---------------------+
| Step | Details |
+===========+=====================+
| Install | - Download the file |
| | - Run the installer |
+-----------+---------------------+
Grid tables are Pandoc’s own syntax, and GitHub and Obsidian show them as text. Running pandoc -f markdown -t gfm on the file turns the grid table into an HTML table that GitHub renders.
A table without a header
Leave the header cells empty:
| | |
| -------- | ------------ |
| Name | Ada Lovelace |
| Born | 1815 |
The header row still exists, drawn on GitHub as a thin empty band. Two-column key and value data also works as a plain list.
How wide tables behave
GFM has no width control. GitHub sizes a table to its content, caps it at the page width and scrolls the overflow sideways. In the source, a wide table becomes rows of pipes too long to edit by eye. Short headers help, and so does swapping rows and columns when a table is wide with few rows.
Make a table without typing pipes
Convert a CSV file or spreadsheet
Save the sheet as CSV (File, Download, CSV in Google Sheets, or Save As, CSV in Excel), then convert it with Pandoc:
pandoc -f csv -t gfm sales.csv -o sales.md
Pandoc pads the columns and escapes pipes in the data. A cell containing a line break makes Pandoc write an HTML table instead.
| Region | Q1 | Q2 | Notes |
|--------|------|------|------------------------|
| North | 1200 | 1350 | Includes the A\|B test |
| South | 980 | 1105 | New office opened |
This Python script needs only the standard library. It strips the byte order mark Excel adds to UTF-8 exports and writes UTF-8 even on Windows:
import csv
import sys
sys.stdout.reconfigure(encoding="utf-8")
with open(sys.argv[1], newline="", encoding="utf-8-sig") as f:
rows = list(csv.reader(f))
def cell(text):
return text.replace("|", r"\|").replace("\n", "<br>")
print("| " + " | ".join(cell(c) for c in rows[0]) + " |")
print("|" + "---|" * len(rows[0]))
for row in rows[1:]:
print("| " + " | ".join(cell(c) for c in row) + " |")
Run it with python csv2md.py sales.csv > sales.md.
Online generators
Tables Generator is free and takes cells pasted from Excel, Google Sheets or LibreOffice Calc. It sets alignment from a toolbar and has a compact mode without padding. TableConvert accepts CSV, Excel, JSON and HTML tables and states that conversion runs in your browser.
In Jotura
Jotura is a notes app that stores each note as a plain Markdown file. In the desktop editor, type /table on an empty line to insert a three-by-three table. Tab moves between cells and adds a row from the last one. Enter breaks the line inside a cell and saves it as <br>. Pasted Markdown tables become editable tables. The editor docs cover the row and column menus and resizing.
The file holds a standard GFM table with the columns padded. Jotura escapes pipes for you, inline code included, which MkDocs shows as a backslash. Dragged column widths go on one jotura: frontmatter line that other apps ignore.
Jotura shows raw HTML as text, except <br> in a cell, so HTML tables for merged cells will not render. Android displays tables without editing them, there is no iOS app, and there are no plugins, so tables have no spreadsheet formulas. With Convert documents automatically on, an .xlsx or .ods file in the vault gets a Markdown copy with one table per sheet. That setting starts off in a vault with an .obsidian/ folder; see documents and converting PDF and Word files.
In Obsidian
Obsidian 1.5 added a table editor to Live Preview, with Insert table in the command palette and row and column actions on right-click. The community plugin Advanced Tables adds auto-formatting, Tab and Enter navigation between cells, spreadsheet formulas and CSV export.
In VS Code
The built-in preview renders tables, and these extensions help you write them:
| Extension | What it adds |
|---|---|
| Markdown All in One | Pads table columns when you run Format Document |
| Markdown Table | Tab between cells, column insertion, and tab-separated text to table |
| Excel to Markdown table | Shift+Alt+V pastes copied Excel cells as a table |
Frequently asked questions
How do I align a column in a Markdown table?
Add colons to that column’s cell in the separator row. :--- aligns left, ---: aligns right, and :---: centers. The setting covers every cell in the column, header included, and no syntax aligns a single cell.
How do I put a pipe character inside a Markdown table cell?
Write \|. In GitHub Flavored Markdown this works inside inline code too, so `a \| b` shows as a | b. Python-Markdown, used by MkDocs, wants a bare pipe inside backticks instead.
How do I add a line break inside a Markdown table cell?
Use the HTML tag <br>, as in First line<br>Second line. GitHub, GitLab, Jotura, Obsidian and VS Code render it. MDX files, including Docusaurus pages, need <br />, and Hugo strips raw HTML until unsafe rendering is enabled.
Can I merge cells in a Markdown table?
No. GitHub Flavored Markdown has no colspan or rowspan. Write the table in HTML with colspan and rowspan attributes, which GitHub and Obsidian render, or split the content into two tables.
Can a Markdown table have no header row?
Not in GitHub Flavored Markdown, which requires one. Leave the header cells empty, as in | | | above the separator row, and the table renders with a thin blank header band.
Why does my Markdown table show as plain text?
Check for a missing blank line above the table and for a separator row whose cell count differs from the header. A renderer that implements plain CommonMark without the table extension also shows the pipes as text.