Skip to content
OneKitly

Pasting a Table Into a Pull Request: What Breaks, and the Two Characters That Break It

Published 7/29/2026 · 15 min read · Developer tools

Daniel Okonkwo

Daniel OkonkwoFront-end developer and tech writer at OneKitly

Web performance · File formats

Checked against 4 sources

View profile
In short

A Markdown table cell can hold anything except two characters. A pipe closes the cell, so it has to be written as \| — the converter does this for you: paste USB-C | 2 m and it emits USB-C \| 2 m, which GitHub renders as one cell. A line break closes the row, and there is no escape for it at all: a cell containing a real newline is impossible in GFM table syntax, so the tool replaces it with the HTML tag <br>, which is the only thing that works. Under the header goes the delimiter row, and it is mandatory — a header line with no row of dashes beneath it is not a table and GitHub renders it as a paragraph with pipes in it. That row also carries the alignment: --- leaves the renderer's default, :-- is left, --: is right, :-: is centred, and a colon anywhere else does nothing. The delimiter row must have the same number of cells as the header or the table is not recognised at all. The spaces that make the source line up are cosmetic and nothing more: | a | b | and |a|b| produce byte-identical HTML, which is why the converter has a Pad columns switch and why turning it off is safe. One detail about the escaping, because correct output looks wrong here: the backslash is escaped before the pipe. A cell that already reads x \| y comes out as x \\\| y, where \\ is a literal backslash and \| is a literal pipe, so GitHub renders the single cell x \| y. Escaping the pipe alone would give x \\| y — a literal backslash followed by a live cell break — and the column would split. Order is the whole of it, and it is why an existing Markdown table can go back through the CSV converter and come out unchanged.

A Markdown table has exactly two forbidden characters in a cell: the pipe and the line break. Here is what each one does, how a converter handles them, why the escape has to be applied in the right order, and why padding never matters.

Two characters, and only two

Almost everything survives a Markdown table cell untouched. Accents, ideographs, emoji, backticks, asterisks, brackets, dollar signs, quotes — none of them mean anything to the table parser, which only ever looks for two things. The pipe ends a cell. The line break ends a row. That is the whole grammar, and every problem you will ever have pasting a table into a pull request is one of those two characters arriving where the parser did not expect it.

The pipe has an escape. Write \| and the parser reads a literal vertical bar instead of a column boundary. The converter applies it for you: its own sample data contains the cell USB-C | 2 m, and running the sample through it yields the row | Câble | USB-C \| 2 m | 9.90 |, which is one row of three cells and not one row of four. Feed it a cell that is nothing but a pipe and you get \| on its own, still one cell. This part is solid, and it is the part most hand-written tables get wrong: a shell command in a documentation table — ps | grep node — silently gains a column and pushes every value one place to the right, which is the kind of defect a reviewer reads past because the table still looks like a table.

The line break has no escape, and this is the part worth internalising: a cell containing a newline is not merely awkward in GFM table syntax, it is impossible. The row ends at the newline, full stop. There are exactly two honest ways out. Replace the break with the HTML tag <br>, which GitHub permits inside a cell and which is what the converter does — a quoted CSV field reading "line one\nline two" comes out as line one<br>line two in a single cell. Or accept that the content does not belong in a table at all and put it in a list or a paragraph underneath. Anything else is wishful thinking; there is no backslash trick, no doubled pipe, no continuation marker.

The delimiter row is not decoration

GitHub Flavored Markdown calls the row of dashes under the header the delimiter row, and the specification is unambiguous about it: it consists of cells whose only content is hyphens, with an optional leading or trailing colon, or both. Without it there is no table. A header line and three data lines with no dashes between them render as four paragraphs of text with pipes in them — which is why a table that looked fine in one editor and broke in the pull request almost always lost that one line to a copy-paste.

Four shapes of delimiter cell mean four different things, and the converter offers all four as an Align setting. A plain --- leaves the alignment to the renderer, which in practice means left in every engine anyone uses. A leading colon, :---, forces left. A trailing colon, ---:, forces right, and is the one you want on any column of numbers. A colon at both ends, :---:, centres. A colon anywhere in the middle of the dashes is not alignment and not valid — the cell has to be colon, dashes, colon, in that order, with nothing else in it. Set the tool to right and a two-column table comes out with | -----: | ---: | underneath, and every number in the column lines up on its last digit in the rendered output.

One rule catches most silent failures: the delimiter row must have the same number of cells as the header row, or the table is not recognised at all. The specification states it outright, and the failure mode is the worst kind — you do not get a broken table, you get no table. A header of four columns over a delimiter row of three renders as literal text, pipes and all. This is exactly what happens when someone adds a column to a table by hand and forgets the dashes. The converter cannot make that mistake because it builds both rows from the same column count, which is a decent argument for generating the table rather than editing one.

Padding is for you, not for GitHub

The converter has a Pad columns switch, on by default, and it changes nothing about the rendered table. With it on, a two-column table comes out as | Item | Qty |, | ------ | --- |, | Widget | 3 |, with the cells squared up. With it off you get | Item | Qty |, | --- | --- |, | Widget | 3 |, ragged. GitHub produces identical HTML from both. The spaces exist so a human reading the diff can see the columns, and for no other reason. Turn padding off when the table is wide and the diff is what people will read; leave it on when the file is edited by hand.

There is a catch worth knowing about if your data is not Latin. The padding is computed by counting characters, and a character is not a column. Feed the converter a cell containing two emoji and it counts two, pads to two, and the source no longer lines up in an editor where each emoji occupies two monospace columns. The same happens with Chinese, Japanese and Korean text, in the other direction from an é written as e plus a combining accent, which is two characters in one column. None of it affects the rendered table, so it is a readability problem and not a correctness one — but if you are producing a wide table of Japanese place names, do not expect the source to look tidy.

Escaping the escape, and guessing the delimiter

A converter that turns every pipe into \| and stops there is wrong on exactly one input, and it is one you meet the moment you re-import a table you wrote yourself: a cell that already contains \|. Escape the pipe alone and x \| y becomes x \\| y, where the doubled backslash is a literal backslash and the pipe behind it is live again — the column splits and nothing warns. The order has to be the other way round. This converter escapes the backslash first and the pipe second, so x \| y comes out x \\\| y, which looks like one backslash too many and is not: GFM reads \\ as a literal backslash and \| as a literal pipe, and renders the single cell x \| y.

The consequence is that a round trip is safe. Convert some data to a Markdown table, copy the table out, save it as CSV, feed it back in — the columns that held pipes come back as they went in, along with everything else. That is worth more than it sounds, because exporting a rendered table to a spreadsheet and re-importing it is an ordinary thing to do the moment a column gets renamed. The sibling tool on this site, the Markdown table generator, closes the same loop from the other end: its parser reads \| as a literal pipe when the delimiter is a pipe and unescapes it on the way in, so a table pasted there comes back identical too.

The delimiter is guessed rather than declared, and the guess is made across records rather than on the header alone. The tool counts commas, semicolons, tabs and pipes outside quotes on up to five records, and a delimiter that gives the same count on every record read beats one that is merely more numerous on the first. That is what settles a header like A|B|C,D: on its own it looks like three pipe-separated columns, but put comma-shaped rows under it and the comma wins, because the comma is the count that repeats. A French export whose header is Nom;Prénom is detected as semicolon-separated either way. The one thing the first record still decides on its own is which delimiters are candidates at all — a header containing none of the four leaves nothing to choose between, and the guess falls back to a comma. If your first line is unusual, set the delimiter by hand.

A workflow that survives review

Export the data as CSV rather than copying cells out of a spreadsheet, because the quoting rules of a CSV file are the only thing that tells the converter where a field with a comma or a line break ends. Paste it, check the delimiter it detected, choose your alignment — right for numbers, default for everything else — and read the first two output lines before you copy. Those two lines are the header and the delimiter row, and if they have a different number of pipes, nothing downstream will render.

Then look for the three cells that cause trouble. Anything with a pipe: check it came out as \| and not as a live column boundary, and remember that a cell which already held a backslash gets that backslash doubled — \\\| is the correct output, not a stray escape. Anything that was multi-line: check it became <br> and decide whether that is really what you want in a table. Anything empty: an empty cell is perfectly legal and renders as an empty cell, so a row of blanks in the middle of your table is data, not damage. If the table is going into a repository rather than a comment, commit it once with padding on so the first reviewer can read the diff, and never reformat it again — a whitespace-only change to a table is thirty lines of noise in a pull request that says nothing.

What the converter does with each awkward cell, and what GitHub renders
Cell contentWhat the tool emitsResult
USB-C | 2 mUSB-C \| 2 mCorrect — one cell containing a visible pipe
A quoted field holding a real newlineline one<br>line twoCorrect — the only thing GFM tables allow; a real newline is impossible
x \| y (already escaped)x \\\| yCorrect — the backslash is escaped first, so GitHub renders the one cell x \| y
An empty fieldNothing between the pipesCorrect — an empty cell is legal and renders empty
A row with fewer fields than the headerPadded with empty cells to the widest rowCorrect — a ragged table would not render at all, so the tool squares it
Two emojiPadded as if they were two columns wideCosmetic only — they occupy four; the rendered table is unaffected
CSV to Markdown tableTurn CSV into a Markdown table, quoting and all: a field may hold the delimiter, a line break or a pipe and the table still lines up. Comma, semicolon (French Excel), tab or pipe, detected or forced.Try the tool

Frequently asked questions

Can I put a line break inside a Markdown table cell?
Not a real one. The newline is what ends a row, so the table syntax has no way to express a cell containing one — there is no escape sequence for it the way \| exists for the pipe. The workaround GitHub accepts is the HTML tag <br>, which is what this converter substitutes: a quoted CSV field with a line break in it becomes one cell reading line one<br>line two. Two honest limits on that. Renderers that strip HTML will show the tag as literal text, and a cell with three or four <br> in it is usually a sign the content wants to be a list under the table rather than a cell inside it.
My table renders as plain text with pipes in it. What did I break?
Almost always the delimiter row. Either it is missing entirely, or it has a different number of cells from the header — the specification says the header row must match the delimiter row in cell count, and if it does not, the table is not recognised and falls back to a paragraph. Count the pipes on line one and line two of your table; they should be the same. The other common cause is a blank line between the header and the delimiter row, which ends the table block before it starts. A third, rarer one: the delimiter cells must contain nothing but hyphens and optional edge colons, so a stray space-dash-space or an em dash pasted by an editor's autocorrect will invalidate the row.
Do the spaces that line up the columns matter?
No. | a | b | and |a|b| produce the same HTML on GitHub, and the converter's Pad columns switch exists purely so the source is readable in an editor. Padding does have one real cost in a repository: because the width of every column is the width of its longest value, editing one cell can change the padding of the whole column, and a one-word change turns into a diff touching every row. If the table lives in a versioned file and is edited often, generating it unpadded produces cleaner diffs. If it is written once and read by humans in the raw file, keep the padding.
Can I use bold, links or code spans inside a table cell?
Yes — inline formatting works normally inside cells, so **bold**, a [link](https://example.com) and a `code span` all render. Block-level constructs do not: no headings, no lists, no fenced code blocks, no nested tables, because all of those need line breaks the row cannot contain. The trap is a code span containing a pipe, such as `ps | grep node`. Backticks do not protect a pipe from the table parser — the cell is split first and the code span is parsed afterwards, so you still have to write `ps \| grep node`. That is one of the few cases where you must escape by hand, because the converter only escapes pipes it can see in the source data.
Why did my French CSV come out as a single column?
Because the header line gave the sniffer nothing to count, and it is the header that decides which delimiters are even in the running. The tool tallies commas, semicolons, tabs and pipes outside quotes across the first few records, and a count that repeats across them beats one that is merely largest on line one — but a delimiter that never appears on the first record is not a candidate at all. A French or German spreadsheet exports with semicolons, since the comma is the decimal mark, and a header reading Nom;Prénom is detected correctly. A header that is a single word with no separator in it is not: there is nothing to count, the guess falls back to a comma, and the whole file arrives as one column. Set the Delimiter option to Semicolon by hand. The same fix applies to a tab-separated export pasted from a terminal, where the tabs may have been turned into spaces in transit and there is genuinely no delimiter left to find.

Articles you may find interesting

All guides
How-toBuilding a Markdown Table From Scratch, Without Counting Dashes by HandThe smallest thing that is still a table is two lines: a header row and a delimiter row. Here is why the second one is mandatory in GitHub Flavored Markdown, where pipe tables do not exist at all, and what a generator does that hand-typing cannot.GuideTransposing a Table Whose Rows Should Have Been ColumnsWhat happens to the header row, what happens to rows of unequal length, what happens to types — and the one thing transposing is regularly mistaken for and cannot do.ExplainerCSV to JSON: The Five Cases That Break Every ConverterQuoted delimiters, embedded newlines, ambiguous types, duplicate headers and encoding. Each one was run through the converter and the exact output is printed here — including the two cases it does not rescue.How-toMarkdown: A Beginner's GuideFormat plain text with a few symbols: # for headings, ** for bold, - for lists. Here's what markdown is, the core syntax, why it's everywhere, and the gotchas.ExplainerSemicolon, Tab, Pipe: Choosing a Delimiter That Survives the TripWhy the reader's language decides the delimiter, what the converter does to the quoting when you switch, what the sep= first line really is, and the count of quoted cells on the same export written five ways.ExplainerWhy Your CSV Breaks Accents and Dates in ExcelThree completely different faults hide behind the same sentence. One is the encoding, one is the separator, one is Excel guessing at types while it opens the file — and the fix for each is different. Here is how to tell them apart in five seconds.

Related tools

This describes how a file format and a renderer behave, verified against the specification cited and against the tool's own code as it stands today. Renderers disagree: GitHub, GitLab, a static-site generator and your editor's preview are four different implementations, and a construct that works in one may not work in another. Nothing here is a guarantee about your pipeline — test the output where it will actually be published, and treat any tool, this one included, as something to check rather than something to trust.

Sources

Spotted a mistake in this article?

Pasting a Table Into a Pull Request: What Breaks, and the Two Characters That Break It — OneKitly