You're debugging an API integration and see this in the request headers:
Authorization: Basic dG9vbHN0by5kZXY6c3VwZXJzZWNyZXQ=
Or you're inspecting a JWT and the payload looks like eyJ1c2VyX2lkIjoxMjM0NX0. Or your email client shows an attachment as a wall of alphanumeric characters. In every case, you're looking at Base64.
This guide covers what Base64 actually does at the bit level, every place you'll encounter it as a developer, and the mistakes that will bite you if you don't understand it.
What Base64 Actually Does
Base64 converts binary data into a string of 64 printable ASCII characters. That's it. It's not encryption, it's not compression — it's a transport encoding that makes binary data safe for systems that only handle text.
The 64 characters are: A-Z (26) + a-z (26) + 0-9 (10) + + and / (2), with = used for padding.
Why Does This Exist?
Many protocols were designed in an era when systems only handled 7-bit ASCII text. Email (SMTP), HTTP headers, JSON, XML, URLs — these are all text-based formats. If you need to send a PNG image through email or embed a font file in CSS, you need a way to represent arbitrary binary bytes as safe text characters.
Base64 is that bridge.
How the Encoding Works (Bit by Bit)
Understanding the algorithm helps you reason about output size and debug edge cases.
Step 1: Take the input and read it as raw bytes (8 bits each).
Step 2: Concatenate all the bits into one stream and split into groups of 6 bits.
Step 3: Map each 6-bit value (0-63) to the corresponding character in the Base64 alphabet.
Step 4: If the total bits aren't divisible by 6, pad the last group with zeros and add = characters to the output.
Here's a concrete example with "Dev":
Character: D e v
ASCII: 68 101 118
Binary: 01000100 01100101 01110110
Concatenated: 010001000110010101110110
Split into 6-bit groups:
010001 000110 010101 110110
Decimal values: 17 6 21 54
Base64 chars: R G V 2
Result: "Dev" → "RGV2"
Three input bytes became exactly four output characters. That ratio is consistent — and it's why Base64 always produces output that's ~33% larger than the input.
The Padding Question
When the input length isn't divisible by 3, the last group needs padding:
- 1 remaining byte → 2 Base64 characters +
== - 2 remaining bytes → 3 Base64 characters +
= - 0 remaining bytes → no padding
"A" → "QQ==" (1 byte → 4 chars with 2 padding)
"AB" → "QUI=" (2 bytes → 4 chars with 1 padding)
"ABC" → "QUJD" (3 bytes → 4 chars, no padding)
The padding tells the decoder exactly how many bytes the original input had. Some implementations (like Base64URL) omit padding entirely, since the decoder can infer it from the output length.
Every Place You'll Encounter Base64
1. HTTP Basic Authentication
The oldest and simplest auth scheme. The client sends username:password as a Base64-encoded string:
GET /api/users HTTP/1.1
Authorization: Basic YWRhOnMzY3IzdA==
Decoding YWRhOnMzY3IzdA== gives you ada:s3cr3t. Anyone who intercepts this header can decode the credentials instantly.
This is encoding, not encryption. Basic auth only makes sense over HTTPS. The Base64 encoding exists because HTTP headers must be ASCII — it's not providing any security.
Want to decode a Basic auth header? Paste it into the Base64 decoder and see the credentials in plain text.
2. JWT Tokens
JWTs use a URL-safe variant of Base64 (Base64URL) for the header and payload:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NSwicm9sZSI6ImFkbWluIn0.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Each dot-separated segment is Base64URL-encoded JSON. The first segment decodes to:
{"alg":"HS256","typ":"JWT"}
The second segment (the payload with your user data) is equally readable. JWTs are signed, not encrypted — anyone with the token can read the claims. Try it with the JWT decoder.
3. Data URIs
Embed files directly in HTML, CSS, or JavaScript:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..." alt="icon" />
.logo {
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz...");
}
When to use data URIs:
- Tiny images under 2KB (icons, 1x1 tracking pixels)
- SVGs that you want to inline without a separate request
- Fonts in CSS
@font-facedeclarations
When not to:
- Anything over ~4KB — the 33% overhead plus inability to cache independently makes separate files more efficient
- Images that appear on multiple pages — a separate file gets cached once
- Dynamic content that changes frequently
4. Email Attachments (MIME)
Email was built for 7-bit ASCII text. When you attach a PDF or image, your email client Base64-encodes the file and embeds it in the message with a MIME boundary:
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.pdf"
JVBERi0xLjQKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFn
ZXMgMiAwIFIKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1R5cGUgL1Bh
Z2VzCi9LaWRzIFszIDAgUl0KL0NvdW50IDEKL01lZGlhQm94IFsw
...
This is why email attachments are always larger than the original file — the 33% Base64 overhead.
5. API Payloads with Binary Data
JSON can't represent raw bytes. When an API needs to accept or return binary data (file uploads, images, encrypted blobs), Base64 encoding is the standard approach:
{
"filename": "avatar.png",
"content_type": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ..."
}
Some APIs use multipart form data instead, which avoids the Base64 overhead by sending binary data in its own MIME part.
6. Cryptographic Output
Hash functions, HMAC signatures, and encryption algorithms produce raw bytes. When you need to store or transmit these values as text, you have two choices: hex encoding (e.g., a1b2c3d4) or Base64 (e.g., obLD1A==).
Base64 is more compact — it represents the same data in fewer characters. A 256-bit SHA-256 hash is 64 hex characters but only 44 Base64 characters.
Base64 vs. Base64URL
Standard Base64 uses + and / as characters 62 and 63. Both have special meaning in URLs:
+is interpreted as a space in URL query strings/is a path separator
Base64URL solves this by substituting:
| Standard Base64 | Base64URL |
|---|---|
+ | - |
/ | _ |
= (padding) | omitted |
JWTs, OAuth tokens, and most modern web APIs use Base64URL. If you're building a new system, prefer Base64URL for any value that might appear in a URL, filename, or HTTP header.
Base64 vs. URL Encoding — Different Problems
These two encodings solve completely different problems:
Base64 converts binary data to ASCII text. Use it when you need to represent non-text data (images, encrypted bytes, arbitrary binary) in a text format.
URL encoding (percent-encoding) escapes special characters in URLs. Hello World becomes Hello%20World. Use it when building query strings or encoding values that go into URLs.
They're complementary, not alternatives. You might Base64-encode an image, then URL-encode the Base64 string to include it as a query parameter. If you need to URL-encode something, try the URL encoder.
Code Examples That Go Beyond the Basics
JavaScript: Handling Unicode Correctly
JavaScript's built-in btoa() only handles Latin-1 characters. Unicode strings (anything with emoji, CJK characters, or accented characters) will throw:
btoa("Hello 🌍"); // ❌ DOMException: The string contains characters outside of the Latin1 range
The fix (modern browsers and Node.js 16+):
// Encode: string → UTF-8 bytes → Base64
function toBase64(str) {
const bytes = new TextEncoder().encode(str);
const binStr = Array.from(bytes, (b) => String.fromCodePoint(b)).join("");
return btoa(binStr);
}
// Decode: Base64 → bytes → UTF-8 string
function fromBase64(b64) {
const binStr = atob(b64);
const bytes = Uint8Array.from(binStr, (c) => c.codePointAt(0));
return new TextDecoder().decode(bytes);
}
toBase64("Hello 🌍"); // "SGVsbG8g8J+MjQ=="
fromBase64("SGVsbG8g8J+MjQ=="); // "Hello 🌍"
Node.js: Buffer API
Node.js has the cleanest Base64 API:
// Encode
Buffer.from("Hello 🌍").toString("base64"); // "SGVsbG8g8J+MjQ=="
Buffer.from("Hello 🌍").toString("base64url"); // "SGVsbG8g8J-MjQ"
// Decode
Buffer.from("SGVsbG8g8J+MjQ==", "base64").toString("utf-8"); // "Hello 🌍"
// Encode a file
const fs = require("fs");
const b64 = fs.readFileSync("image.png").toString("base64");
const dataUri = `data:image/png;base64,${b64}`;
Python
import base64
# Standard Base64
encoded = base64.b64encode(b"Hello \xf0\x9f\x8c\x8d").decode("ascii")
# 'SGVsbG8g8J+MjQ=='
decoded = base64.b64decode(encoded)
# b'Hello \xf0\x9f\x8c\x8d'
# URL-safe variant
url_safe = base64.urlsafe_b64encode(b"Hello \xf0\x9f\x8c\x8d").decode("ascii")
# 'SGVsbG8g8J-MjQ=='
Command Line
# Encode
echo -n "Hello World" | base64
# SGVsbG8gV29ybGQ=
# Decode
echo "SGVsbG8gV29ybGQ=" | base64 --decode
# Hello World
# Encode a file
base64 < image.png > image.b64
# macOS uses -D instead of --decode
echo "SGVsbG8=" | base64 -D
Common Mistakes and How to Avoid Them
Mistake 1: Using Base64 as "Encryption"
Seen in production more often than you'd think:
// ❌ This is not security
const "encrypted" = btoa(JSON.stringify({ password: "hunter2" }));
Base64 is instantly reversible. If you need to protect data, use actual encryption (AES-256-GCM for symmetric, RSA/ECDH for asymmetric), then optionally Base64-encode the ciphertext for transport.
Mistake 2: Ignoring the 33% Size Overhead
Base64-encoding a 5MB image produces ~6.67MB of text. In a JSON API response, that's a significant payload increase. Consider:
- Returning a URL to the file instead of the file contents
- Using multipart responses for binary data
- Setting up a CDN for static assets
Mistake 3: Double-Encoding
// ❌ Encoding twice
const double = btoa(btoa("Hello")); // "U0dWc2JHOD0="
// ✅ Encode once
const single = btoa("Hello"); // "SGVsbG8="
If you're seeing garbled output, check whether you're accidentally encoding or decoding twice. This happens often when middleware and application code both handle encoding.
Mistake 4: Line Breaks in Base64
Some implementations (like MIME and PEM) insert line breaks every 76 characters. If you're parsing Base64 and seeing unexpected failures, strip line breaks and whitespace before decoding:
const clean = base64String.replace(/[\s\n\r]/g, "");
const decoded = atob(clean);
When Not to Use Base64
- Large file transfers — use binary protocols (multipart upload, gRPC, WebSocket binary frames)
- Storage — store binary data as binary in your database (BLOB columns, S3 objects)
- Security — use encryption, not encoding
- Compression — Base64 makes data larger, not smaller. Compress first (gzip), then Base64-encode if needed
Try It
Paste any string into the Base64 encoder/decoder to encode or decode instantly. It handles UTF-8, shows the output size, and lets you switch between encode and decode with one click.
