Base64 shows up everywhere: data URIs in CSS, JWTs, email attachments, API tokens, embedded images in JSON payloads. Most developers copy-paste it around without thinking too hard about what it's doing. That's usually fine — until it isn't, and you're staring at a corrupted image or a token that won't validate.

Here's the short version of what Base64 is, when it earns its place, and the mistakes that catch people out.

What Base64 actually is

Base64 is an encoding, not encryption or compression. It takes arbitrary binary data and represents it using only 64 printable ASCII characters: A–Z, a–z, 0–9, plus + and /. The = character is used for padding at the end.

The mechanics are simple. It reads your input three bytes at a time — that's 24 bits — and slices those 24 bits into four groups of 6 bits. Each 6-bit group (a value from 0 to 63) maps to one character in the Base64 alphabet. Three bytes in, four characters out. That's why Base64 output is always about 33% larger than the original data.

When your input length isn't a multiple of three, the encoder pads the final block with = signs so the output length stays a multiple of four.

Why it exists

A lot of systems were built to move text, not raw bytes. Email (SMTP) historically only handled 7-bit ASCII. URLs have reserved characters. JSON is a text format and has no native way to hold binary. If you try to stuff raw bytes through these channels, something in the pipeline will mangle a byte it thinks is a control character or a delimiter.

Base64 solves this by making binary data look like safe, boring text that survives transport intact. That's the whole point.

Common real-world uses

  • Data URIs — embedding a small icon directly in HTML or CSS: data:image/png;base64,iVBOR...
  • JWTs — the header and payload are Base64URL-encoded JSON.
  • HTTP Basic Auth — username:password encoded and sent in the Authorization header.
  • Email attachments — MIME uses Base64 to carry files.
  • Storing binary blobs in JSON or a text column when you have no better option.

A worked example

Let's encode the word Man. Those three characters are ASCII bytes 77, 97, 110, which in binary is:

M        a        n
01001101 01100001 01101110

Concatenate those 24 bits and re-split into 6-bit chunks:

010011 010110 000101 101110
  19     22      5      46

Map each value to the Base64 alphabet (0=A, 1=B, … 19=T, 22=W, 5=F, 46=u):

Man  ->  TWFu

In code, you rarely do this by hand. Every language has it built in:

// JavaScript (browser)
btoa("Man");            // "TWFu"
atob("TWFu");           // "Man"

// Node.js — better, handles binary properly
Buffer.from("Man").toString("base64");        // "TWFu"
Buffer.from("TWFu", "base64").toString();      // "Man"

// Python
import base64
base64.b64encode(b"Man")   # b'TWFu'
base64.b64decode(b"TWFu")  # b'Man'

When you just need to check a token, decode a JWT payload, or eyeball what a data URI contains, a quick Base64 Encoder / Decoder in the browser is faster than opening a REPL.

Gotchas that will bite you

1. It is not security

Base64 is reversible by anyone, instantly. If someone can read the encoded string, they can read the original. Never treat it as a way to hide passwords, keys, or PII. HTTP Basic Auth encodes credentials in Base64 precisely so they're easy to decode — the security comes from TLS, not the encoding.

2. Standard vs URL-safe alphabets

The + and / characters are problematic in URLs and filenames. Base64URL swaps them for - and _, and often drops the = padding. JWTs use this variant. If you decode a JWT with a standard Base64 decoder and it fails, this mismatch is usually why.

Standard:  a+b/c==
URL-safe:  a-b_c

3. btoa/atob and Unicode

In the browser, btoa throws on characters outside Latin-1. Feed it an emoji or accented text and it breaks:

btoa("café");   // ❌ InvalidCharacterError

The fix is to encode to UTF-8 bytes first:

// Encode
btoa(String.fromCharCode(...new TextEncoder().encode("café")));

// Or in modern environments, just use Buffer/Uint8Array APIs.

4. Line wrapping

Some encoders (MIME, OpenSSL) insert newlines every 64 or 76 characters. A decoder expecting a single unbroken string may choke on those. If you're passing data between tools, know whether wrapping is on.

5. Size cost

That 33% overhead adds up. Inlining a 200 KB image as a data URI means ~270 KB of text in your HTML, and it can't be cached separately. For anything but tiny assets, a real URL is better.

When not to use it

Reach for Base64 only when a channel genuinely can't carry binary. If you control both ends and the transport handles bytes — a binary WebSocket frame, a file upload, a proper blob column — skip it. Encoding binary you didn't need to encode just wastes bandwidth and CPU.

Base64 is one of those quiet, dependable tools you'll use for the rest of your career. It does one job — making bytes look like safe text — and it does it well. Keep the alphabet variants and the "it's not encryption" rule in mind, and it'll never surprise you.

Photo by Jake Walker on Unsplash