Skip to content

The Three Invisible Bytes at the Start of Your CSV

Your CSV has a column called `id`. Your code looks for a column called `id`. It is not there. Print the header and it says `id`. The problem is three bytes at the very start of the file that no editor displays, that Excel on Windows once needed, and that half the software in the world now has to work around.

Ganesh Patil·4 min read
Before and after comparison: Three bytes before your first column header.

The same two-line CSV, saved four ways:

encodingbytes for a,b\n1,2\nfirst four bytes
UTF-8, LF861 2c 62 0a
UTF-8 with BOM, LF11ef bb bf 61
UTF-8, CRLF1061 2c 62 0d
UTF-16LE with BOM18ff fe 61 00

The second row is the one that causes support tickets. Three extra bytes — ef bb bf — sit in front of the first character, and every text editor in existence hides them.

What a BOM is for

A byte order mark is the code point U+FEFF written at the start of a file. It was invented for UTF-16, where it does real work: ff feand fe fftell a reader whether the two-byte units are little-endian or big-endian. Without it, a UTF-16 file is ambiguous.

UTF-8 has no byte order to mark. Its units are single bytes, so the question does not arise. A UTF-8 BOM is therefore not a byte order mark at all — it is a signature saying "this file is UTF-8", and the Unicode standard explicitly does not recommend it.

It exists because Microsoft Excel on Windows, for many years, would open a UTF-8 CSV as if it were the local single-byte codepage unless the BOM was present. A file full of caféwould open as café. Adding the BOM was the only reliable fix, so a great deal of export code adds it, and now every consumer of those files has to cope.

What it breaks

Header matching. The first column name is not id, it is id. String comparison fails, dictionary lookup fails, and printing the header shows id because the terminal renders the BOM as nothing at all. This is the single most common symptom.

Numeric parsing. If the first column is a number, 1is not a valid integer, and the error message will quote the value as 1.

Concatenation. Joining ten CSVs puts a BOM in the middle of the result nine times, and a BOM in the middle of a file is just a stray invisible character.

JSON. A JSON file with a BOM is invalid JSON by the specification, and many parsers reject it with a syntax error at position 0 that appears to point at a perfectly good {.

Shell scripts and config files. A script beginning with a BOM before #!will not execute, because the kernel does not find the shebang at byte zero.

How to see it

The BOM is invisible by design, so you need a tool that shows bytes:

head -c 16 file.csv | hexdump -C

If the output starts ef bb bf, the file has a UTF-8 BOM. If it starts ff feor fe ff, the file is UTF-16 and your problems are larger.

file file.csvwill also say "UTF-8 Unicode (with BOM) text", which is quicker to read and slightly less reliable across systems.

Removing it

sed -i '1s/^\xEF\xBB\xBF//' file.csv

Or in most languages, decode with an encoding that consumes it: Python's utf-8-sig, .NET's StreamReaderwith detection enabled, and Node's TextDecoder with ignoreBOM: false.

The important part is doing it once, at the point where files enter your system, rather than defensively stripping it in every function that touches a string.

Should you write one?

If a human will open the file in Excel on Windows: yes. This is still the situation the BOM exists for, and the alternative is your users seeing mojibake and concluding your export is broken.

If a machine will read the file: no. It is three bytes of noise that the receiving end has to be built to tolerate.

If both: emit it, and tell the other end. A BOM is easy to strip when you know it is there and mystifying when you do not.

There is a case for splitting the export into a "download for Excel" button and an API endpoint, precisely because those two consumers want incompatible files. That is a slightly ugly interface reflecting a genuinely awkward reality, and it is better than one file that is subtly wrong for both.

The related invisible characters

A BOM is the most common invisible character to appear at the start of a file, and not the only one that breaks matching. Non-breaking spaces from word processors, zero-width spaces from copy-pasted web pages, and soft hyphens from justified text all render as nothing and all defeat exact comparison. If a value looks right and will not match, the first move is always to look at the bytes rather than at the characters.