Base64 a CSV Column
Base64 encoding turns a column of text into the ASCII-only form that APIs, JWTs, data URIs and email headers expect, and decoding turns it back. Both directions go through UTF-8 bytes, so accented text survives. A cell that is not valid base64 is left alone and counted rather than quietly mangled. It runs in this tab.
Need percent-encoding instead? The URL encoder is next door.
The line that breaks most implementations
In a browser, base64 encoding usually means btoa. Try it on a name with an accent in it:
btoa("café")
// InvalidCharacterError: String contains an invalid character
btoa wants one byte per character and refuses anything above code point 255. A tool built straight on it either throws on the first accented row, or silently drops the high bits and produces base64 that decodes to garbage. Neither failure is visible in a spreadsheet, which is where the file usually goes next.
Everything here goes through UTF-8 bytes first. café encodes to Y2Fmw6k=, which is the base64 of the five bytes 63 61 66 c3 a9, and decodes back to café exactly. That is what every other language means by base64 of a string, and it is what a receiving API will expect.
The encoding is also chunked rather than done in one call, because String.fromCharCode(...bytes) blows the JavaScript argument limit somewhere around a hundred kilobytes. A hash never gets that long. A cell holding a small document does.
Worked example: round trip on a contact list
The example button loads contacts.csv:
id,full_name,email,company,signed_up
001,Ada Lovelace,ada@analytical.example,Analytical Engines Ltd,2024-01-15
002,Grace Hopper,grace.hopper@navy.example,Naval Systems,2024-02-02
Tick email, leave the direction on Encode, and a new column appears beside it:
id,full_name,email,email_base64,company,signed_up
001,Ada Lovelace,ada@analytical.example,YWRhQGFuYWx5dGljYWwuZXhhbXBsZQ==,...
002,Grace Hopper,grace.hopper@navy.example,Z3JhY2UuaG9wcGVyQG5hdnkuZXhhbXBsZQ==,...
Two things to notice. The new column is called email_base64 and sits immediately after its source, not at the far right, so the pair reads together. And 001 in the id column is still 001: cells are strings from the moment the file is read to the moment it is written, so nothing turns an identifier into the number one along the way.
Now switch Direction to Decode and tick email_base64 instead. You get email_base64_decoded holding the original addresses, character for character. That round trip is worth doing once on any file you are about to hand to another system, because it proves the encoding is the one they will be decoding.
Two alphabets, and when the difference matters
Standard base64 uses 64 characters ending in + and /, and pads to a multiple of four with =. All three of those are unsafe in the places base64 most often ends up.
- In a URL, a slash starts a new path segment and a plus is sometimes read as a space. The URL-safe alphabet swaps them for
-and_, which need no escaping anywhere in a URL. - In a filename, a slash is a directory separator on every operating system there is.
- In a JWT, the convention is URL-safe with the padding stripped. That is why the segments of a token have no equals signs on them and why pasting one into a standard decoder sometimes fails.
So the two switches pair up: set Alphabet to URL-safe and Padding to Strip = and you get exactly the form a JWT or an OAuth state parameter uses. Both controls only appear while the direction is Encode, because decoding needs neither: it accepts both alphabets and repairs missing padding on its own.
Decoding is where the honesty matters
Encoding always works. Decoding is where a column of real data starts producing surprises, and there are three of them.
Missing padding. A base64 string whose length is not a multiple of four is still perfectly decodable; the padding is a convention rather than information. JWT segments and query parameters almost never carry it. The padding is restored before decoding, so Y2Fmw6k and Y2Fmw6k= both give you café.
Bytes that are not text. Base64 encodes bytes, and plenty of those bytes are images, keys or compressed blobs rather than characters. Decoding runs through TextDecoder("utf-8", { fatal: true }), so a value whose bytes are not valid UTF-8 throws rather than coming back as a row of replacement diamonds that looks like a successful decode. That cell keeps its original text and is counted.
Cells that were never base64. A column where only some rows are encoded is common: an export where one system wrote raw values and another wrote encoded ones. Those rows are rejected on the alphabet check, left exactly as they were, and reported with the first few quoted back at you. That count is the useful number, because it tells you the column holds two formats.
Whitespace inside a value is tolerated, since base64 that has been through an email header or a PEM file arrives wrapped across lines.
What base64 is not
Worth saying plainly, because it is the reason a lot of people arrive here. Base64 is an encoding, not a cipher. Anyone with the file can decode it in one line, on this page or anywhere else. It hides nothing.
If the goal is to share a file without exposing the values in it, encoding a column achieves precisely nothing, and it makes the file larger by a third into the bargain. What you want is a salted hash, which is one-way and still joins correctly against another file hashed with the same salt. That page says more about why the salt is the part that matters.
The legitimate reasons to encode a column are all about transport rather than secrecy: an API field that only accepts ASCII, a value that has to survive a system with opinions about quoting, a payload going into a data URI, a blob column being moved between databases. For all of those, this is the right tool and the round trip is the thing to check.
Details worth knowing
- Blank cells stay blank. The base64 of an empty string is an empty string, so nothing would change, but a blank is skipped before the transform runs and counted separately. A column of missing values does not come back full of identical tokens.
- Several columns in one pass. Ticking three columns produces three new ones, each named after its source. Nothing is overwritten: a new column whose name is already taken gets a numeric suffix.
- The output grows. Base64 is four characters for every three bytes, so an encoded column is about a third larger, and a little more with padding. Worth knowing before you encode a column of long text in a file that is already near the 100 MB ceiling.
- Every control recomputes at once. There is no Run button. Switching the alphabet or the direction rewrites the whole result immediately, which is what makes the round-trip check quick.
- Spreadsheets are read too. Drop an .xlsx and the first sheet with data in it is used, with the sheet name reported. The output is always CSV.
Frequently Asked Questions
Does it handle accented characters and emoji?
Yes, in both directions. Values are converted to UTF-8 bytes before encoding rather than being fed to btoa, which refuses anything above code point 255. cafe with an accent encodes to Y2Fmw6k= and decodes back exactly. That is what every other language means by base64 of a string.
What is the URL-safe alphabet for?
Standard base64 ends in plus and slash and pads with equals signs, and all three are awkward in a URL or a filename: a slash starts a new path segment, a plus is sometimes read as a space. The URL-safe alphabet swaps them for hyphen and underscore. Combine it with stripped padding and you get the form JWTs use.
Do I need to strip the padding?
Only if the system receiving the value expects it stripped, which JWTs and OAuth parameters do. Decoding here works either way, since the padding is a convention rather than information and it is restored automatically before the decode runs.
Why did some cells fail to decode?
Three usual reasons: the value was never base64 to begin with, which happens when a column holds a mix of raw and encoded rows; the bytes decode fine but are not text, such as an image or a key; or the string is malformed. In every case the cell keeps its original value and the count is reported with examples.
Why does the decoder reject something instead of returning it?
Because the alternative is worse. Without the fatal flag, a decoder handed invalid bytes silently substitutes replacement characters and hands back a string of diamonds that looks like a successful decode. Refusing and counting it tells you the truth about the column.
Is base64 a way to protect data?
No. It is an encoding, not a cipher, and anyone holding the file can decode it in one line. It also makes the file about a third larger. If the goal is to share data without exposing values, use a salted hash instead, which is one-way and still joins against another file hashed the same way.
Can I encode more than one column at a time?
Yes. Tick as many as you like and each gets its own new column named after its source. If a name is already taken, the new column gets a numeric suffix rather than overwriting anything.
Does the file get uploaded?
No. There is no upload endpoint on this page. The file is read, encoded and rewritten by JavaScript in your own tab, and nothing is stored between visits.
Related
Encode it, then decode it back
Free, no account, no upload. Pick the column, pick the alphabet, check the round trip, take the CSV.
Back to the encoder