Base64 is one of those things every developer copies around without stopping to think about what it does. You paste an image into a CSS file, you decode a chunk of a JWT, you shove some binary into a JSON payload — and it just works. Until it doesn't. Understanding the mechanics saves you from a whole category of confusing bugs.
What Base64 actually is
Base64 is an encoding scheme that represents binary data using only 64 printable ASCII characters: A-Z, a-z, 0-9, plus + and /. The = character is used for padding.
The core idea is simple. It takes your input three bytes at a time (24 bits), then splits those 24 bits into four groups of 6 bits. Each 6-bit group maps to one of the 64 characters. That's why the output is always a multiple of four characters, and why encoded data is roughly 33% larger than the original — you're turning every 3 bytes into 4.
The key thing to internalise: Base64 is not encryption and not compression. It's a reversible transformation whose only job is to make binary data survive systems that expect text. Anyone can decode it in a second.
When you'd actually use it
Base64 earns its keep whenever binary data needs to travel through a text-only channel:
- Email attachments. The original use case. SMTP was designed for 7-bit text, so binary attachments get Base64-encoded (via MIME).
- Data URIs. Embedding a small image or font directly in CSS or HTML with
data:image/png;base64,...to avoid an extra HTTP request. - JSON and XML payloads. These formats are text, so if you need to include raw bytes — a thumbnail, a file blob — Base64 is the standard way.
- JWTs. The header and payload segments of a JSON Web Token are Base64URL-encoded (more on that variant below).
- Basic auth headers.
Authorization: Basic dXNlcjpwYXNzis justuser:passin Base64. Which is exactly why Basic auth needs HTTPS.
If your data is already text and passes cleanly through your pipeline, you don't need Base64. Encoding a plain string "just in case" only bloats it.
A short worked example
Let's encode the string Hi! by hand to see the mechanics.
Text: H i !
ASCII: 72 105 33
Binary: 01001000 01101001 00100001
Regroup into 6-bit chunks:
010010 000110 100100 100001
Decimal: 18 6 36 33
Base64: S G k h
Result: "Hi!" -> "SGkh"Because Hi! is exactly 3 bytes, it maps neatly to 4 output characters with no padding. Try a 1- or 2-byte input and you'll see = padding fill the gap.
In practice you'll never do this manually. Most languages have it built in:
// JavaScript (browser)
btoa("Hi!"); // "SGkh"
atob("SGkh"); // "Hi!"
// Node.js
Buffer.from("Hi!").toString("base64"); // "SGkh"
Buffer.from("SGkh", "base64").toString(); // "Hi!"
# Python
import base64
base64.b64encode(b"Hi!") # b'SGkh'
base64.b64decode("SGkh") # b'Hi!'When you just need to decode a token or check a value quickly, a browser-based Base64 Encoder / Decoder beats spinning up a REPL — paste, read, move on.
Gotchas that will bite you
1. btoa/atob and Unicode
The browser's btoa only handles Latin-1 (bytes 0–255). Feed it an emoji or accented characters and it throws:
btoa("café"); // InvalidCharacterErrorThe fix is to encode to UTF-8 bytes first:
function utf8ToBase64(str) {
return btoa(
String.fromCharCode(...new TextEncoder().encode(str))
);
}
utf8ToBase64("café"); // "Y2Fmw6k="2. Standard vs URL-safe Base64
The + and / characters have special meaning in URLs, and = gets percent-encoded. So the Base64URL variant swaps + → -, / → _, and usually drops the padding. JWTs use this. If you decode a JWT segment with a standard decoder, it may fail or produce garbage. Know which variant you're dealing with.
3. It's not security
I've seen "encrypted" config files that were just Base64. If your goal is to hide a secret, Base64 does nothing. It's trivially reversible. Use real encryption, and treat any Base64 value in a request as fully readable by the client.
4. The 33% size overhead
Encoding a 1 MB file gives you about 1.33 MB of text. For a tiny inline icon that's fine. For large assets in a data URI, you're paying for the bloat on every page load and losing the browser's ability to cache the file separately. Draw the line at a few kilobytes.
5. Line breaks in encoded output
MIME-style Base64 wraps lines at 76 characters. Some decoders choke on the newlines, others require them. If a decode fails on data that looks valid, strip whitespace first and try again.
Rules of thumb
- Reach for Base64 only when binary data must pass through a text-only medium.
- Always encode text to UTF-8 bytes before Base64-encoding it.
- Check whether you need the standard or URL-safe alphabet.
- Never rely on it to conceal anything.
- Watch the size budget for inline assets.
Base64 is a small, well-defined tool. Most of the pain comes from expecting it to do something it was never meant to — hide data, shrink data, or magically handle character sets. Keep its actual job in mind (binary → safe text and back), respect the two alphabet variants, and it'll stay boring in the best possible way.
Photo by Jake Walker on Unsplash