Base64 Encoding & Decoding: Complete Guide
Base64 is one of those technologies you encounter constantly as a developer — in email attachments, data URIs, JWTs, API payloads, and config files. This guide explains how it works, how to use it correctly in JavaScript and Python, and the one mistake that trips up nearly everyone: confusing encoding with encryption.
⚡ Try the Tool
Encode text or files to Base64 instantly — auto-detect, drag-and-drop, and byte-size stats.
Open Base64 Encoder & Decoder →What Is Base64?
Base64 is a binary-to-text encoding scheme. It takes any binary data — a file, a string, raw bytes — and represents it as a string of printable ASCII characters. The name "Base64" refers to the 64 characters used: A–Z (26), a–z (26), 0–9 (10), plus two extra characters typically + and /.
The algorithm works by treating every 3 bytes of input (24 bits) as a group and splitting them into four 6-bit chunks. Each 6-bit chunk is mapped to one of the 64 characters in the alphabet. If the input length isn't divisible by 3, one or two = padding characters are appended to make the output length a multiple of 4.
| Input bytes | Binary | Base64 groups (6-bit) | Output |
|---|---|---|---|
M a n (3 bytes) |
010011010110000101101110 | 010011 | 010110 | 000101 | 101110 | TWFu |
M a (2 bytes) |
0100110101100001 | 010011 | 010110 | 0001(00) | pad | TWE= |
M (1 byte) |
01001101 | 010011 | 01(0000) | pad | pad | TQ== |
The size overhead is always around 33%: 3 input bytes become 4 output characters. For a 1 MB file, the Base64 output is roughly 1.37 MB.
Why Is Base64 Used?
Binary data causes problems in systems designed to handle text. Null bytes, non-printable control characters, and encoding ambiguities break protocols like SMTP (email), HTTP headers, XML, and JSON. Base64 solves this by guaranteeing the output contains only printable ASCII characters that are safe everywhere.
Common use cases:
- Email attachments — MIME encoding uses Base64 to attach binary files to email messages
- Data URIs — Embedding images and fonts directly in HTML/CSS (
data:image/png;base64,iVBOR...) - JWTs (JSON Web Tokens) — Header and payload sections are Base64url-encoded
- API payloads — Sending binary data (files, images) in JSON requests
- Config files — Storing binary keys or certificates in YAML or .env files
- PEM certificates — TLS certificates are Base64-encoded DER format wrapped in
-----BEGIN CERTIFICATE-----
Base64 in JavaScript
Encoding ASCII text (simple)
For ASCII text, JavaScript's built-in btoa() function works directly:
// Encode
const encoded = btoa('Hello, World!');
console.log(encoded); // "SGVsbG8sIFdvcmxkIQ=="
// Decode
const decoded = atob('SGVsbG8sIFdvcmxkIQ==');
console.log(decoded); // "Hello, World!"
Encoding UTF-8 text (emoji, international characters)
btoa() only handles Latin-1 characters. For Unicode strings (emoji, Chinese, Arabic, etc.), you must encode to UTF-8 bytes first:
// Encode UTF-8 text to Base64
function toBase64(str) {
const bytes = new TextEncoder().encode(str);
const binary = Array.from(bytes, b => String.fromCharCode(b)).join('');
return btoa(binary);
}
// Decode Base64 back to UTF-8 text
function fromBase64(b64) {
const binary = atob(b64);
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
return new TextDecoder().decode(bytes);
}
// Test with emoji
console.log(toBase64('Hello 🌍')); // "SGVsbG8g8J+MjQ=="
console.log(fromBase64('SGVsbG8g8J+MjQ==')); // "Hello 🌍"
Encoding a file to Base64 (browser)
To encode a file selected via <input type="file">:
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = () => {
const bytes = new Uint8Array(reader.result);
const binary = Array.from(bytes, b => String.fromCharCode(b)).join('');
resolve(btoa(binary));
};
reader.onerror = reject;
});
}
// Usage
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', async () => {
const b64 = await fileToBase64(input.files[0]);
console.log(b64.substring(0, 50) + '...');
});
Encoding files in Node.js
const fs = require('fs');
// File to Base64
const fileBuffer = fs.readFileSync('./image.png');
const base64 = fileBuffer.toString('base64');
console.log(base64.substring(0, 50) + '...');
// Base64 to file
const decoded = Buffer.from(base64, 'base64');
fs.writeFileSync('./output.png', decoded);
Base64 in Python
Encoding and decoding strings
import base64
# Encode string to Base64
text = "Hello, World! 🌍"
encoded = base64.b64encode(text.encode('utf-8'))
print(encoded) # b'SGVsbG8sIFdvcmxkISDwn4yN'
# Decode Base64 back to string
decoded = base64.b64decode(encoded).decode('utf-8')
print(decoded) # "Hello, World! 🌍"
Encoding and decoding files
import base64
# File to Base64
with open('image.png', 'rb') as f:
encoded = base64.b64encode(f.read())
# Save as text
with open('image.b64', 'w') as f:
f.write(encoded.decode('ascii'))
# Base64 back to file
with open('image.b64', 'r') as f:
data = base64.b64decode(f.read())
with open('output.png', 'wb') as f:
f.write(data)
Standard Base64 vs. URL-Safe Base64
Standard Base64 uses + and / as characters 62 and 63. In URLs, + is interpreted as a space in query strings, and / separates path segments — breaking any Base64 string embedded in a URL.
URL-safe Base64 (RFC 4648 §5) replaces these:
+→-(hyphen)/→_(underscore)- Trailing
=padding is typically omitted
| Variant | Chars 62/63 | Padding | Used In |
|---|---|---|---|
| Standard (RFC 4648 §4) | + and / | = | MIME, PEM, most Base64 |
| URL-safe (RFC 4648 §5) | - and _ | usually omitted | JWTs, URL params, filenames |
| MIME (RFC 2045) | + and / | = | Email, 76-char line wrap required |
// JavaScript: convert standard Base64 to URL-safe
function toBase64Url(b64) {
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
// Python: built-in URL-safe variant
import base64
url_safe = base64.urlsafe_b64encode(data).rstrip(b'=')
decoded = base64.urlsafe_b64decode(url_safe + b'==')
MIME Line Wrapping
The MIME standard (RFC 2045) requires Base64-encoded data in email to be broken into lines of no more than 76 characters, each ending with CRLF (\r\n). This existed because some legacy email servers couldn't handle long lines.
Modern systems typically accept unwrapped Base64, but you'll still see line wrapping in:
- PEM certificates (
.pemfiles, 64 chars per line) - Email MIME attachments
- Legacy configuration files
// Add MIME line wrapping (76 chars)
function mimeWrap(b64) {
return b64.match(/.{1,76}/g).join('\n');
}
// Python
import base64, textwrap
wrapped = '\n'.join(textwrap.wrap(
base64.b64encode(data).decode(), 76
))
The #1 Mistake: Treating Base64 as Encryption
Base64 is not encryption. Anyone can decode it in seconds. Never use Base64 to "hide" passwords, API keys, tokens, or sensitive user data. You will get breached.
This mistake shows up constantly in codebases:
// ❌ Wrong — this is NOT security
const "hidden" = btoa('my_api_key_12345');
config.apiKey = hidden; // anyone can call atob() on it
// ✅ Right — use environment variables
const apiKey = process.env.API_KEY; // never in source code
Base64 provides zero confidentiality. It only changes the representation of data, not its accessibility. A developer who decodes your Base64-"encoded" API key will have your API key in under 5 seconds.
For actual security:
- Secrets & API keys — Use environment variables, a secrets manager (AWS Secrets Manager, HashiCorp Vault), or a platform-managed config
- Passwords — Hash with bcrypt, Argon2, or scrypt (never encrypt, never Base64)
- Data at rest — AES-256-GCM symmetric encryption
- Data in transit — TLS 1.3
Performance Considerations
Base64 adds ~33% size overhead. For small payloads (under 100 KB) this rarely matters. For large files, it matters more:
- A 10 MB image becomes ~13.7 MB as Base64
- A 100 MB video becomes ~137 MB — you probably shouldn't use Base64 for this
- Browser memory: Base64 strings are held in memory; very large encodings can cause noticeable slowdowns
The general rule: use Base64 for data under ~1 MB. For larger files, use direct file URLs, object storage (S3, R2), or streaming — not inline Base64.
btoa() = binary to ASCII = encode to Base64
atob() = ASCII to binary = decode from Base64
The "a" stands for ASCII, not "all" — easy to mix up.
Detecting Whether a String Is Base64
Auto-detecting Base64 isn't always reliable, but a good heuristic is:
function isBase64(str) {
// Strip whitespace and line breaks (MIME-wrapped Base64)
const clean = str.replace(/[\s\r\n]/g, '');
if (clean.length === 0) return false;
// Must only use Base64 alphabet
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(clean)) return false;
// Length must be divisible by 4 after padding
const padded = clean.padEnd(clean.length + (4 - clean.length % 4) % 4, '=');
// Attempt decode to confirm it's valid
try { atob(padded); return true; } catch { return false; }
}
This heuristic produces false positives for short strings that happen to only contain Base64 characters (e.g. the word "Hello" is valid Base64 syntax but it's probably plain text). Context matters — if the string is 128+ characters and uses the full alphabet, it's almost certainly Base64.
Summary
- Base64 converts binary data to printable ASCII — 3 bytes → 4 characters, +33% size
- Use it when you need to transmit binary over text-only channels (email, JSON, HTML)
- In JavaScript:
btoa()/atob()for ASCII; useTextEncoderfor UTF-8 - In Python:
base64.b64encode()/base64.b64decode() - URL-safe Base64 replaces
+→-and/→_— required for JWTs and URLs - MIME line wrap at 76 chars for email compatibility
- Base64 is not encryption. Never use it to protect sensitive data.
⚡ Encode or Decode Base64 Instantly
Paste text, drop a file, switch between encode/decode/auto — all client-side, no data sent anywhere.
Open Base64 Tool →