Base64 is one of those things every developer uses but few fully understand. You've seen it in JWT tokens, CSS data URIs, email attachments, and API responses. You know it makes binary data look like random letters, but when should you actually use it — and when is it the wrong tool?
This guide explains the Base64 algorithm clearly, covers every practical use case, shows how to encode and decode in the most common languages, and points out the mistakes developers make when reaching for Base64 out of habit.
What Is Base64?
Base64 is a binary-to-text encoding scheme. It converts binary data (bytes) into a string of printable ASCII characters using a 64-character alphabet:
- Uppercase letters:
A–Z(26 characters) - Lowercase letters:
a–z(26 characters) - Digits:
0–9(10 characters) - Two additional characters:
+and/ - Padding character:
=
That's 64 characters — hence the name.
Why 64?
Because every ASCII character that's "safe" to transmit through text-only systems is representable with a small, stable set. Original email systems, HTTP headers, and many protocols were designed for ASCII text, not arbitrary bytes. Base64 bridges the gap: take any binary data, turn it into a safe text string.
How It Works
Base64 processes input in 3-byte groups and converts each group into 4 Base64 characters.
- Take 3 bytes (24 bits)
- Split into four 6-bit groups
- Map each 6-bit group to one Base64 character using a lookup table
Example: encoding Man
ASCII values: M=77, a=97, n=110
Binary: 01001101 01100001 01101110
Split into 6-bit groups:
010011 | 010110 | 000101 | 101110
Map to Base64 alphabet:
010011 = 19 = T
010110 = 22 = W
000101 = 5 = F
101110 = 46 = u
Result: TWFu
If the input isn't a multiple of 3 bytes, padding (=) fills the output to a multiple of 4 characters.
Base64 Is Not Encryption
This is the most important thing to understand: Base64 encoding is not encryption and provides no security.
Encoding: reversible transformation, no key required. Anyone can decode it.
Encryption: transformation that requires a key to reverse. Without the key, the data is unreadable.
If you Base64 encode a password before storing it, that password is exposed. If you Base64 encode a credit card number in an API response, that number is exposed to anyone who intercepts the response.
Base64 is for format transformation, not protection. Use it to make binary data text-safe. Use proper encryption (AES-256, RSA, etc.) when you need to protect data.
When to Use Base64
Base64 is the right choice in these situations:
1. Embedding Images in CSS or HTML
Data URIs let you embed small images directly in your CSS without a separate HTTP request:
.icon {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABmJLR0QA/wD/AP+gvaeTAAAADklEQVQI12NgQAIABQABNjN9GQAAAALaA==');
}
This is useful for small icons (under 10KB) where the request overhead would cost more than the encoding overhead. For larger images, always use separate files — the 33% size increase and browser caching limitations make data URIs counterproductive.
2. Encoding Binary Data in JSON APIs
JSON only supports Unicode strings. If your API needs to return binary data (an image thumbnail, a cryptographic signature, raw bytes), you have two options: Base64 encode it, or use a binary-safe format like MessagePack.
{
"filename": "avatar.png",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs...",
"encoding": "base64"
}
Always include an encoding field to make it explicit that the value is Base64 encoded.
3. HTTP Basic Authentication
The Authorization: Basic header encodes credentials as Base64(username:password):
Authorization: Basic dXNlcjpwYXNzd29yZA==
Decoded: user:password
Important: this is only "safe" because HTTPS encrypts the entire HTTP exchange. Over plain HTTP, Basic Auth is trivially intercepted. The Base64 encoding is not security — the TLS layer is.
4. JWT Tokens
JWT tokens use Base64url encoding (a URL-safe variant) for the header and payload:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwiZXhwIjoxNzExMTQ0ODAwfQ.signature
Decoded header: {"alg":"HS256","typ":"JWT"}
Decoded payload: {"sub":"user123","exp":1711144800}
The payload is Base64url encoded, not encrypted. Any JWT can be decoded and the payload read without the signing secret. Never put sensitive data (passwords, SSNs, private keys) in a JWT payload unless the token is also encrypted (JWE, not just JWS).
5. Email Attachments (MIME)
Email protocols were designed for ASCII text. Binary attachments (PDFs, images, Office documents) are Base64 encoded in MIME messages. You see this in raw email source or when parsing email programmatically.
6. Cryptographic Data Exchange
Public keys, certificates, and signatures are often distributed in PEM format, which is Base64 encoded DER data wrapped in header lines:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
When NOT to Use Base64
For Large Binary Files
A 1MB file becomes ~1.33MB Base64 encoded. For file upload APIs, use multipart/form-data instead:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
This streams the binary directly without the 33% overhead.
For URLs
Standard Base64 uses +, /, and = — characters that have special meaning in URLs. If you need to put Base64 in a URL, use Base64url instead (- instead of +, _ instead of /, no padding).
Never URL-encode standard Base64 to put it in a URL — the double encoding creates maintenance headaches.
As "Obfuscation"
Encoding a password in Base64 and calling it "encrypted" is a security vulnerability. It is not obfuscation — it's a well-known, standardized format. Security auditors check for this specifically.
How to Encode and Decode Base64
Browser (JavaScript)
The browser provides btoa() and atob() for ASCII strings:
// Encode
const encoded = btoa('Hello, World!');
// "SGVsbG8sIFdvcmxkIQ=="
// Decode
const decoded = atob('SGVsbG8sIFdvcmxkIQ==');
// "Hello, World!"
For Unicode strings (anything outside ASCII), you need to handle the encoding manually:
// Encode Unicode string to Base64
function encodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) =>
String.fromCharCode('0x' + p1)
));
}
// Decode Base64 to Unicode string
function decodeUnicode(str) {
return decodeURIComponent(atob(str).split('').map(c =>
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
).join(''));
}
encodeUnicode('日本語');
// "5pel5pys6Kqe"
Node.js
Node.js uses Buffer for Base64 operations:
// Encode string to Base64
const encoded = Buffer.from('Hello, World!').toString('base64');
// "SGVsbG8sIFdvcmxkIQ=="
// Decode Base64 to string
const decoded = Buffer.from('SGVsbG8sIFdvcmxkIQ==', 'base64').toString('utf8');
// "Hello, World!"
// Encode binary file to Base64
const fs = require('fs');
const imageData = fs.readFileSync('image.png');
const imageBase64 = imageData.toString('base64');
// Base64url (no padding, URL-safe characters)
const urlSafe = Buffer.from('Hello+World/Test=').toString('base64url');
Python
import base64
# Encode bytes to Base64
encoded = base64.b64encode(b'Hello, World!')
# b'SGVsbG8sIFdvcmxkIQ=='
# Decode Base64 to bytes
decoded = base64.b64decode('SGVsbG8sIFdvcmxkIQ==')
# b'Hello, World!'
# String encoding: encode the string to bytes first
text = 'Hello, World!'
encoded_str = base64.b64encode(text.encode('utf-8')).decode('utf-8')
# 'SGVsbG8sIFdvcmxkIQ=='
# Base64url (URL-safe variant)
url_safe = base64.urlsafe_b64encode(b'Hello+World')
url_decoded = base64.urlsafe_b64decode(url_safe)
# Encode a file
with open('image.png', 'rb') as f:
image_base64 = base64.b64encode(f.read()).decode('utf-8')
Go
import (
"encoding/base64"
"fmt"
)
// Encode
encoded := base64.StdEncoding.EncodeToString([]byte("Hello, World!"))
fmt.Println(encoded) // SGVsbG8sIFdvcmxkIQ==
// Decode
decoded, err := base64.StdEncoding.DecodeString("SGVsbG8sIFdvcmxkIQ==")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(decoded)) // Hello, World!
// URL-safe Base64 (for JWTs, URLs)
urlEncoded := base64.URLEncoding.EncodeToString([]byte("Hello, World!"))
urlDecoded, _ := base64.URLEncoding.DecodeString(urlEncoded)
// URL-safe without padding
rawEncoded := base64.RawURLEncoding.EncodeToString([]byte("Hello, World!"))
Rust
use base64::{Engine, engine::general_purpose};
// Encode
let encoded = general_purpose::STANDARD.encode(b"Hello, World!");
println!("{}", encoded); // SGVsbG8sIFdvcmxkIQ==
// Decode
let decoded_bytes = general_purpose::STANDARD.decode("SGVsbG8sIFdvcmxkIQ==").unwrap();
let decoded = String::from_utf8(decoded_bytes).unwrap();
println!("{}", decoded); // Hello, World!
// URL-safe (no padding)
let url_safe = general_purpose::URL_SAFE_NO_PAD.encode(b"Hello, World!");
Bash / Command Line
# Encode
echo -n "Hello, World!" | base64
# SGVsbG8sIFdvcmxkIQ==
# Decode
echo "SGVsbG8sIFdvcmxkIQ==" | base64 -d
# Hello, World!
# Encode a file
base64 image.png > image.b64
# Decode a file
base64 -d image.b64 > image.png
Base64 vs Base64url: Which to Use?
| Feature | Base64 (Standard) | Base64url |
|---|---|---|
| Characters |
A-Z, a-z, 0-9, +, /
|
A-Z, a-z, 0-9, -, _
|
| Padding | Required (=) |
Optional |
| URL-safe | No (+, / need escaping) |
Yes |
| Used in | MIME email, PEM files, data URIs | JWTs, OAuth, URL parameters |
Use standard Base64 for: email, PEM/certificate files, data URIs, HTTP Basic Auth headers.
Use Base64url for: anything appearing in a URL, JWT tokens, OAuth state parameters, filename-safe identifiers.
Performance: How Much Does Base64 Increase Size?
Base64 encodes 3 bytes into 4 characters — a 33% size increase. This matters at scale:
| Original Size | Base64 Size | Increase |
|---|---|---|
| 1 KB | ~1.37 KB | +370 bytes |
| 10 KB | ~13.7 KB | +3.7 KB |
| 100 KB | ~137 KB | +37 KB |
| 1 MB | ~1.37 MB | +370 KB |
Impact on HTTP Requests
When you embed Base64-encoded images in CSS, you remove an HTTP request but increase the CSS file size. The tradeoff:
- Worth it: Small icons under 5-10KB where the HTTP request overhead (TCP connection, headers, DNS, latency) exceeds the size cost
- Not worth it: Photos, illustrations, or any image over ~10KB — the size increase outweighs the request savings
Modern HTTP/2 connection multiplexing reduces the "too many requests" problem significantly. With HTTP/2, multiple resources are served over one connection, making the "save a request" argument for Base64 data URIs weaker than it was in the HTTP/1.1 era.
Impact on JavaScript Bundles
Bundlers (webpack, Rollup, esbuild) can inline small assets as Base64 data URIs automatically:
// webpack config — inline assets under 8KB as Base64
module.exports = {
module: {
rules: [{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8KB
}
}
}]
}
};
For Vite:
// vite.config.js
export default {
build: {
assetsInlineLimit: 4096 // 4KB — inline as Base64 if smaller
}
};
These settings handle the tradeoff automatically based on file size.
Using DevPlaybook Base64 Tool
For quick encoding and decoding without writing code, DevPlaybook's Base64 tool handles:
- Text strings (including Unicode)
- File uploads (encode small files to Base64)
- Paste-and-decode for instant inspection
- Base64url variant support
- Copy-to-clipboard output
Useful scenarios: inspecting a JWT payload, checking what an API is returning in a Base64 field, encoding a small image for a CSS data URI, or verifying that your encoding implementation matches expected output.
Debugging Base64 Issues
"Invalid character in Base64 string"
You're using standard Base64 where Base64url is expected (or vice versa). Check for +/- and //_ characters.
Output doesn't match expected value
Check the input encoding. btoa('hello') in JavaScript operates on Latin-1 characters. If your string contains characters above U+00FF, you'll get a DOMException. Use the Unicode-safe wrapper shown earlier.
Decoding produces garbled text
The Base64 might be correct, but you're decoding as the wrong character encoding. If the original was UTF-8, decode as UTF-8:
const text = new TextDecoder('utf-8').decode(
Uint8Array.from(atob(encoded), c => c.charCodeAt(0))
);
Missing padding errors
Base64url often omits the = padding. Before decoding standard Base64, add padding:
function addPadding(base64) {
return base64 + '='.repeat((4 - base64.length % 4) % 4);
}
Conclusion
Base64 is a practical, widely-used encoding that solves a specific problem: making binary data safe to transmit through text-only channels. Use it for embedding images in CSS, encoding binary data in JSON APIs, HTTP Basic Auth credentials, JWT tokens, and PEM files. Avoid it for large files (use multipart instead), URL parameters (use Base64url), and anything where you need actual security (use encryption).
For quick encode/decode operations, DevPlaybook's Base64 tool handles any input in the browser without sending data anywhere. For programmatic use, every major language has built-in support — use the examples above as a reference.
Working with JWT tokens that contain Base64url-encoded payloads? The JWT Decoder shows the full decoded structure at a glance. Formatting the JSON output? Try the JSON Formatter.
Level Up Your Dev Workflow
Found this useful? Explore DevPlaybook — cheat sheets, tool comparisons, and hands-on guides for modern developers.
🛒 Get the DevToolkit Starter Kit on Gumroad — 40+ browser-based dev tools, source code + deployment guide included.
Top comments (0)