Base64 shows up everywhere once you start looking: data: URLs embedding an image, a JWT payload, an email attachment, an API key baked into a config. All of them use the same trick — representing arbitrary binary data as plain ASCII text so it can travel safely through systems that only expect text.
This guide explains Base64 from first principles: the problem it solves, the exact algorithm that turns three bytes into four characters, the standard and URL-safe alphabets defined by RFC 4648, what the = padding is for, and the single most important thing to understand — Base64 is an encoding, not encryption. It hides nothing.
The Problem Base64 Solves
Computers store everything as bytes — sequences of 8 bits that can hold any value from 0 to 255. Text is just one interpretation of those bytes. Many systems, though, were built to move *text* around: email bodies, URLs, JSON strings, XML documents, HTTP headers. Hand them raw binary — the bytes of a PNG, a compressed archive, an encryption key — and things break. A null byte can truncate a string; a byte that happens to match a control character can be mangled; anything outside the printable ASCII range may not survive the trip.
Base64 sidesteps the whole problem by re-expressing binary data using only 64 characters that every text system agrees on: A–Z, a–z, 0–9, and two symbols. The result is a string that looks like gibberish but is guaranteed to be safe printable text. You can drop it into a JSON value, a URL, or an email and be confident it arrives byte-for-byte intact.
How Base64 Actually Works
The name is the algorithm: work in base 64. Take the input as a stream of bits and regroup it. Binary is 8 bits per byte; Base64 is 6 bits per character (because 2^6 = 64). The least common multiple of 8 and 6 is 24, so Base64 processes the input 3 bytes (24 bits) at a time and emits 4 characters (24 bits).
Walk through encoding the word Man. Its three ASCII bytes are 77, 97, 110, which in binary is 01001101 01100001 01101110. Line those 24 bits up and re-slice them into four 6-bit groups:
Text: M a n
ASCII: 77 97 110
Bits: 01001101 01100001 01101110
Regroup: 010011 010110 000101 101110
Value: 19 22 5 46
Base64: T W F uEach 6-bit value (0–63) is an index into the Base64 alphabet. Index 19 is T, 22 is W, 5 is F, 46 is u — so Man encodes to TWFu. Decoding just reverses the process: map each character back to its 6-bit value, concatenate the bits, and re-slice into 8-bit bytes.
// In the browser (and modern Node):
const encoded = btoa("Man"); // "TWFu"
const decoded = atob("TWFu"); // "Man"
// Note: btoa/atob work on binary strings, not Unicode text.
// For arbitrary strings, encode to UTF-8 bytes first:
const bytes = new TextEncoder().encode("café");
const b64 = btoa(String.fromCharCode(...bytes)); // "Y2Fmw6k="Padding: What the '=' Means
Base64 works in 3-byte groups, but real data rarely divides evenly by three. When the final group has only 1 or 2 bytes left over, the encoder pads the output to a multiple of four characters using the = sign. = is never a data character — it exists purely to signal how many bytes the last group actually held.
- 3 bytes left → 4 characters, no padding (e.g.
TWFu). - 2 bytes left → 3 characters + one
=(e.g.TWE=forMa). - 1 byte left → 2 characters + two
=(e.g.TQ==forM).
Because the count of = tells the decoder exactly how many trailing bytes to keep, some contexts drop padding entirely to save characters — most notably JWTs and URL parameters. A compliant decoder can usually reconstruct the length without it, which is why you will see unpadded Base64 in the wild.
Standard vs URL-Safe Base64 (RFC 4648)
The canonical specification is RFC 4648. It defines two alphabets. The first 62 characters are identical in both — A–Z, a–z, 0–9. The disagreement is only about the last two symbols and the padding, and it matters enormously depending on where the string will live.
| Index 62 | Index 63 | Padding | Safe in URLs? | |
|---|---|---|---|---|
| Standard Base64 | + | / | = | No |
| URL-safe Base64 | - | _ | often omitted | Yes |
Standard Base64 uses + and /. Both are problematic in a URL: + is interpreted as a space in query strings, and / is a path separator. The URL-safe variant swaps them for - (hyphen) and _ (underscore), neither of which needs escaping in a URL or filename. Padding = is also reserved in URLs, so URL-safe Base64 commonly drops it.
Converting between the two is a simple character swap: replace +→- and /→_ (and strip =) to go URL-safe, and reverse it to go back. If a decoder rejects a string, a mismatched alphabet is one of the first things to check.
Base64 Is Not Encryption
This is the single most common misconception, and it has real security consequences. Base64 is a reversible, public, keyless transformation. Anyone can decode any Base64 string instantly — there is no secret involved. It provides zero confidentiality. Encoding a password, an API secret, or personal data in Base64 does *not* protect it; it only makes it slightly less obvious to a human skimming the text.
// "Encoding" a secret protects nothing:
const secret = btoa("hunter2"); // "aHVudGVyMg=="
atob("aHVudGVyMg=="); // "hunter2" — trivially reversed by anyoneThe distinction: encoding changes the representation of data for compatibility (Base64, URL-encoding, UTF-8); encryption protects data so only holders of a key can read it (AES, RSA). If you need confidentiality, you need encryption. Use Base64 only for what it is for — moving binary safely through text channels.
Where You'll Actually Meet Base64
- Data URLs —
data:image/png;base64,iVBORw0KGgo...embeds an image directly in HTML or CSS, no separate request. - JWTs — the header and payload are Base64URL-encoded JSON; see our JWT guide.
- Email (MIME) — attachments and non-ASCII bodies are Base64-encoded so they survive text-only mail transport.
- HTTP Basic Auth — the
Authorizationheader carriesbase64(username:password)(which is exactly why Basic Auth requires HTTPS). - Binary in JSON — JSON has no binary type, so a byte blob is stored as a Base64 string field.
That last case is the one JSON developers hit most: since JSON has no native binary type, any file bytes, hash, or key you want to embed in a payload has to become a Base64 string first, then get decoded on the other side.
Whenever you need to inspect a token, embed an asset, or verify what a Base64 string actually contains, decoding it locally is the fastest check. A client-side tool does the transform in your browser — nothing is uploaded — so you can paste a token or a data: URL and read the bytes without sending sensitive data anywhere.