Base64 is the silent workhorse of the modern web — every JWT, every data: URI, every email
attachment, every Authorization: Basic header rides on it. Defined in RFC 4648,
it converts arbitrary bytes into 64 printable ASCII characters at the cost of a fixed
33% size overhead. This guide explains exactly how the encoding works bit-by-bit, when to
use the URL-safe variant, JavaScript pitfalls with Unicode, performance implications, and security
misconceptions — including the critical fact that Base64 is not encryption.
What Is Base64?
Base64 is a binary-to-text encoding scheme that represents arbitrary bytes using only 64 printable ASCII characters. It exists because many internet protocols — SMTP for email, HTTP headers, JSON, URL query strings — were designed for text and break (or are mangled by intermediaries) when fed raw binary.
📖 Definition — Base64 is defined in RFC 4648. The standard alphabet uses A-Z, a-z, 0-9, +, and /, with = as the padding character.
How the Encoding Actually Works
Base64 takes 3 bytes (24 bits) at a time and re-groups them into 4 chunks of 6 bits. Each 6-bit value (0–63) maps to one character of the alphabet.
Input: "Man"
Bytes: M (77) a (97) n (110)
Binary: 01001101 01100001 01101110
Re-group: 010011 010110 000101 101110
Decimal: 19 22 5 46
Base64: T W F u
Output: "TWFu"
💡 The math is simple: 3 bytes × 8 bits = 24 bits = 4 × 6 bits. So every 3 input bytes always produce exactly 4 output characters, with padding for the leftovers.
The Base64 Alphabet
| Value | Char | Value | Char | Value | Char | Value | Char |
|---|---|---|---|---|---|---|---|
| 0 | A | 16 | Q | 32 | g | 48 | w |
| 1 | B | 17 | R | 33 | h | 49 | x |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 25 | Z | 26 | a | 51 | z | 52 | 0 |
| 53 | 1 | ... | ... | 61 | 9 | 62 | + |
| 63 | / | Padding: = | |||||
Padding & the 33% Overhead
When the input length is not a multiple of 3, Base64 pads with = so the output is always a
multiple of 4 characters:
| Input length mod 3 | Output ends with | Example |
|---|---|---|
| 0 | (no padding) | "Man" → "TWFu" |
| 1 | == | "M" → "TQ==" |
| 2 | = | "Ma" → "TWE=" |
⚠️ Base64 always inflates data by ~33% (4/3 ratio). For a 100 KB binary, expect ~133 KB Base64 output. This is why embedding large images as data: URIs hurts page-load size.
Standard vs URL-Safe (Base64url)
Standard Base64 uses + and /, both of which have special meaning in URLs. The
URL-safe variant (also called Base64url) substitutes them:
| Position | Standard | URL-Safe | Why |
|---|---|---|---|
| 62 | + | - | + becomes space when URL-decoded |
| 63 | / | _ | / is path separator |
| padding | = | (omitted) | = must be percent-encoded |
JWTs, S3 pre-signed URLs, OAuth tokens, and most modern web APIs use Base64url. Always toggle the URL-Safe option in our Base64 Encoder when working with these.
Common Use Cases
| Use case | Variant | Notes |
|---|---|---|
data: URIs in CSS/HTML | Standard | Best for icons < 2 KB |
| Email attachments (MIME) | Standard | 76-char line wrap (RFC 2045) |
| JWT (header + payload) | Base64url | No padding |
| HTTP Basic Auth | Standard | base64("user:pass") |
| Binary in JSON APIs | Either | Pick one and document it |
| S3 pre-signed URLs | Base64url | HMAC signature encoding |
| WebAuthn / passkeys | Base64url | Cred IDs & challenges |
| QR code payloads | Standard | QR alphanumeric mode is more efficient |
Base64 in JavaScript
// ASCII-only (legacy, fails on Unicode!)
btoa("Hello"); // "SGVsbG8="
atob("SGVsbG8="); // "Hello"
// Unicode-safe encode (UTF-8)
const utf8 = new TextEncoder().encode("Café ☕");
const b64 = btoa(String.fromCharCode(...utf8)); // legacy way
// or modern (Node 16+, modern browsers):
const b64m = Buffer.from("Café ☕", "utf8").toString("base64");
// URL-safe variant
const urlSafe = b64.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/g, "");
// Decode URL-safe back
function fromUrlSafe(s) {
const pad = "=".repeat((4 - (s.length % 4)) % 4);
return atob((s + pad).replace(/-/g, "+").replace(/_/g, "/"));
}
🚫 btoa() throws InvalidCharacterError on any character above U+00FF. Always UTF-8-encode first when dealing with non-Latin text or emoji.
Other Languages Cheat Sheet
| Language | Encode | Decode |
|---|---|---|
| Python 3 | base64.b64encode(b"...") | base64.b64decode("...") |
| Python URL-safe | base64.urlsafe_b64encode(...) | base64.urlsafe_b64decode(...) |
| Node.js | Buffer.from(s).toString("base64") | Buffer.from(b64, "base64").toString() |
| Bash | echo -n "..." | base64 | echo "..." | base64 -d |
| PHP | base64_encode($s) | base64_decode($s) |
| Go | base64.StdEncoding.EncodeToString(b) | base64.StdEncoding.DecodeString(s) |
| Java | Base64.getEncoder().encodeToString(b) | Base64.getDecoder().decode(s) |
| Rust | BASE64_STANDARD.encode(&b) | BASE64_STANDARD.decode(&s) |
Security: Encoding ≠ Encryption
🚫 Base64 is NOT encryption. It is fully reversible by anyone who sees the string. Never use it to "hide" passwords, API keys, or PII.
Use TLS — Base64-encoded credentials in HTTP Basic Auth must travel over HTTPS or anyone on the wire reads them.
Sign tokens — JWTs are Base64url-encoded but the security comes from the HMAC/RSA signature, not the encoding.
Validate input — A decoded payload is still untrusted input. Apply normal length limits, content-type checks, and schema validation.
Common Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
Using btoa on Unicode | InvalidCharacterError | UTF-8 encode first |
| Mixing Base64 and Base64url | Decode fails on JWT | Choose one and document |
Stripping = from standard Base64 | Length not divisible by 4 | Re-add padding before decoding |
| Base64 inside Base64 | Triples the size | Encode once at the boundary |
Embedding 1 MB image as data: URI | Slow page load, large CSS | Use real <img> with HTTP cache |
| Storing Base64 in DB blob column | 33% wasted storage | Use BYTEA/BLOB and store raw bytes |
Tools
- 🔧 Base64 Encoder & Decoder — Text + file modes, URL-safe toggle, MIME line-wrap, live size overhead.
- 🔧 JWT Decoder — Inspect Base64url-encoded JWT payloads.
- 🔧 URL Encoder — Combine with Base64url for query-safe payloads.
- 🔧 Hash Generator — Verify integrity of decoded data.
Frequently Asked Questions
Is Base64 encryption?
No. It is a reversible encoding, not a cipher. Anyone can decode it instantly with built-in tools. Use TLS for confidentiality, and JWS/JWE for cryptographic protection.
Why is Base64 33% larger than the original?
Because each 6-bit Base64 character carries less information than an 8-bit byte. The exact ratio is 4/3 ≈ 1.333, so 3 input bytes always become 4 output characters.
What is the difference between Base64 and Base64url?
Base64url is a variant defined in RFC 4648 §5 that uses - and _ instead of + and /, and typically omits the = padding. It is safe for URLs, filenames, and JWT segments.
Should I embed images as Base64 data URIs?
Only for very small images (< 2 KB) where saving the HTTP request matters more than the 33% size overhead. For larger images, regular <img> tags benefit from HTTP caching and parallel downloads.
Why does btoa("é") fail in JavaScript?
Because btoa only handles characters in the Latin-1 range (U+0000–U+00FF interpreted as bytes). For Unicode, encode to UTF-8 bytes first using TextEncoder or Buffer.from(s, "utf8"), then Base64 those bytes.
Is it safe to store passwords in Base64?
Absolutely not. Base64 is reversible. Use a memory-hard password hash (Argon2id, bcrypt) for storage, never Base64.
References
- 📄 RFC 4648 — The Base16, Base32, and Base64 Data Encodings
- 📄 RFC 2045 — MIME Part One (Base64 in email)
- 📄 RFC 7519 — JSON Web Token (JWT)
- 📄 MDN — Base64 Glossary
- 📄 RFC 7617 — HTTP Basic Authentication
🚀 Free ToolZilla tools used in this article
All client-side, no signup, no upload — open them in a new tab while you read:
- 🔧 Base64 Encoder & Decoder — try it free in your browser.
- 🔧 JWT Decoder — try it free in your browser.
- 🔧 URL Encoder & Decoder — try it free in your browser.
- 🔧 Hash Generator (MD5/SHA) — try it free in your browser.
- 🧰 Browse all 60+ free tools →
Base64 is a binary-to-text encoding, not encryption. Use the standard alphabet
for email and HTML, Base64url for URLs and JWTs. Expect a fixed 33% size inflation,
keep data: URIs to small icons, and never roll your own UTF-8 handling — let
TextEncoder / Buffer.from(..., "utf8") or our
Base64 Encoder do it for you.

