← All guides

Base64 Encoding Explained (and Where It Silently Breaks)

People sometimes treat Base64 like it's a light form of encryption. It isn't. It's a way of representing arbitrary bytes using only 64 printable ASCII characters (A-Z, a-z, 0-9, +, /), so you can safely embed binary data — an image, a certificate, a token — inside something that only understands text, like JSON, XML, or an email body. Anyone can decode it in one line. Don't use it to hide anything sensitive.

Three bytes of input become four Base64 characters. That's why encoded output is always about 33% larger than the original.

Where it actually gets used

The Unicode trap

This is the one that actually causes bugs. In a browser, calling btoa("café") throws InvalidCharacterError, because btoa only understands Latin1 (0-255), and é falls outside that range as a raw JS string character. The fix is to UTF-8 encode first:

btoa(unescape(encodeURIComponent("café")))
// or, cleaner, via TextEncoder:
const bytes = new TextEncoder().encode("café")
const base64 = btoa(String.fromCharCode(...bytes))

This tool does the TextEncoder version under the hood, so pasting emoji or accented characters won't blow up — but if you're hand-rolling this in your own code, this is the bug you'll hit first.

Standard vs URL-safe alphabet

Standard Base64 uses + and /, both of which have special meaning in a URL. If you're putting Base64 in a query string or a filename, you want the URL-safe variant, which swaps +- and /_, and usually drops the trailing = padding. JWTs use URL-safe Base64 for exactly this reason — the token needs to survive being stuck in a header or a URL without extra escaping.

Try the Base64 Encode / Decode