Base64 Encoding Explained: How It Works and When to Use It
Published March 2026 · 12 min read
What Is Base64 Encoding?
Base64 is a binary-to-text encoding scheme that represents binary data using a set of 64 printable ASCII characters. It was designed to solve a fundamental problem in computing: how do you transmit binary data — images, files, encrypted payloads — through systems that were built to handle only text?
Many protocols and data formats, such as email (SMTP), HTML, JSON, and XML, are inherently text-based. They cannot reliably transmit raw binary data because certain byte values may be interpreted as control characters, delimiters, or simply cause corruption. Base64 bridges this gap by converting arbitrary binary data into a safe, text-friendly representation that can pass through any text-based channel without modification.
The name "Base64" comes from the fact that the encoding uses exactly 64 characters to represent data. Compare this with Base16 (hexadecimal, using 16 characters) or Base256 (raw bytes). The choice of 64 is deliberate — it's a power of 2 (26 = 64), which makes the conversion from binary straightforward, while using enough characters to keep the encoded output reasonably compact.
Key takeaway: Base64 is not encryption, compression, or hashing. It is a reversible encoding that makes binary data safe for text-based transport. Anyone can decode it back to the original data.
How Base64 Encoding Works
The Base64 encoding process transforms every 3 bytes (24 bits) of input into 4 Base64 characters (each representing 6 bits). Here is the step-by-step algorithm:
The Base64 Alphabet
Base64 uses 64 characters drawn from the ASCII printable set. The alphabet consists of:
A-Z(indices 0–25)a-z(indices 26–51)0-9(indices 52–61)+(index 62)/(index 63)=(padding character, not part of the 64)
Step-by-Step Encoding Process
Let's walk through encoding the string "Hi!" to understand exactly how it works.
Step 1: Convert each character to its ASCII byte value
| Character | ASCII Decimal | Binary (8-bit) |
|---|---|---|
| H | 72 | 01001000 |
| i | 105 | 01101001 |
| ! | 33 | 00100001 |
Step 2: Concatenate all bits into one continuous binary string
01001000 01101001 00100001 → 010010000110100100100001 (24 bits total)
Step 3: Split into 6-bit groups
010010 000110 100100 100001
Step 4: Convert each 6-bit group to its decimal value and map to a Base64 character
| 6-bit Group | Decimal | Base64 Character |
|---|---|---|
| 010010 | 18 | S |
| 000110 | 6 | G |
| 100100 | 36 | k |
| 100001 | 33 | h |
Result: "Hi!" encodes to "SGkh". Since the input was exactly 3 bytes, no padding is needed.
Understanding Padding
Base64 processes data in chunks of 3 bytes. When the input length is not a multiple of 3, padding is required. The = character is used as a pad:
- 1 byte remaining: The single byte (8 bits) is padded with 4 zero bits to make 12 bits (two 6-bit groups). Two
==are appended to fill the 4-character block. - 2 bytes remaining: The two bytes (16 bits) are padded with 2 zero bits to make 18 bits (three 6-bit groups). One
=is appended. - Exactly 3 bytes: No padding needed.
For example, encoding "Hi" (only 2 bytes) produces "SGk=" — notice the single = pad. Encoding "H" (1 byte) produces "SA==" — two pad characters.
The Base64 Index Table
Here is the complete mapping from 6-bit values (0–63) to their corresponding Base64 characters. This table is the core of the encoding and decoding process.
| Index | Char | Index | Char | Index | Char | Index | Char |
|---|---|---|---|---|---|---|---|
| 0 | A | 16 | Q | 32 | g | 48 | w |
| 1 | B | 17 | R | 33 | h | 49 | x |
| 2 | C | 18 | S | 34 | i | 50 | y |
| 3 | D | 19 | T | 35 | j | 51 | z |
| 4 | E | 20 | U | 36 | k | 52 | 0 |
| 5 | F | 21 | V | 37 | l | 53 | 1 |
| 6 | G | 22 | W | 38 | m | 54 | 2 |
| 7 | H | 23 | X | 39 | n | 55 | 3 |
| 8 | I | 24 | Y | 40 | o | 56 | 4 |
| 9 | J | 25 | Z | 41 | p | 57 | 5 |
| 10 | K | 26 | a | 42 | q | 58 | 6 |
| 11 | L | 27 | b | 43 | r | 59 | 7 |
| 12 | M | 28 | c | 44 | s | 60 | 8 |
| 13 | N | 29 | d | 45 | t | 61 | 9 |
| 14 | O | 30 | e | 46 | u | 62 | + |
| 15 | P | 31 | f | 47 | v | 63 | / |
The character selection is intentional. Uppercase letters come first (A=0), followed by lowercase (a=26), digits (0=52), and finally + and /. These 64 characters are universally safe in ASCII text systems, making them ideal for transport encoding.
Base64 in Web Development
Base64 encoding appears throughout modern web development. Here are the most common use cases you will encounter.
Data URIs — Embedding Images in CSS and HTML
Data URIs allow you to embed small files directly in HTML or CSS, eliminating an extra HTTP request. The format is:
data:[mediatype];base64,[encoded-data]
<!-- Example: Embedding a small PNG image -->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..." alt="icon" />
/* In CSS */
.icon {
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxu...');
}When to use Data URIs: Small icons (under 2–4 KB) where the overhead of an HTTP request exceeds the ~33% size increase from Base64. For larger images, external files with proper caching are more efficient.
API Authentication — HTTP Basic Auth
HTTP Basic Authentication encodes the username and password pair in Base64 and sends it in the Authorization header:
// Credentials: username = "admin", password = "secret123" // Combined: "admin:secret123" // Base64 encoded: "YWRtaW46c2VjcmV0MTIz" Authorization: Basic YWRtaW46c2VjcmV0MTIz
Warning: Base64 is not encryption. The credentials above can be decoded by anyone who intercepts the header. Always use Basic Auth over HTTPS, and prefer more secure authentication methods like OAuth 2.0 or API keys when possible.
JSON Web Tokens (JWTs)
JWTs use a variant called Base64URL encoding for the header and payload sections. A JWT looks like header.payload.signature, where the header and payload are Base64URL-encoded JSON objects:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // header (Base64URL) eyJzdWIiOiIxMjM0NTY3ODkwIn0. // payload (Base64URL) SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c // signature
Email Attachments (MIME)
Email was originally designed to transmit 7-bit ASCII text. MIME (Multipurpose Internet Mail Extensions) uses Base64 to encode binary attachments — images, PDFs, documents — so they can travel through email servers that only understand text. Every attachment you send or receive is Base64-encoded behind the scenes.
Storing Binary Data in JSON or XML
JSON and XML have no native binary type. When you need to include binary data — such as a cryptographic signature, a thumbnail, or a file hash — Base64 is the standard way to represent it as a string within these formats.
{
"filename": "photo.jpg",
"content": "R0lGODlhAQABAIAAAAAAAP///yH5BAEA...",
"contentType": "image/jpeg"
}Base64 in JavaScript
JavaScript provides several ways to work with Base64 encoding. Each approach has different capabilities and limitations.
btoa() and atob() — The Built-in Functions
The browser provides two global functions for Base64: btoa() (binary-to-ASCII, i.e., encode) and atob() (ASCII-to-binary, i.e., decode).
// Encoding
const encoded = btoa("Hello, World!");
// "SGVsbG8sIFdvcmxkIQ=="
// Decoding
const decoded = atob("SGVsbG8sIFdvcmxkIQ==");
// "Hello, World!"Limitation: btoa() only handles Latin-1 characters (code points 0–255). Passing a string with characters outside this range — such as emoji or CJK characters — throws a DOMException.
Handling Unicode Strings
To encode strings containing Unicode characters beyond Latin-1, you first need to convert them to a byte sequence. Here is the modern approach using the TextEncoder API:
// Modern approach: TextEncoder + Uint8Array
function toBase64(str: string): string {
const encoder = new TextEncoder();
const bytes = encoder.encode(str);
const binString = Array.from(bytes, (byte) =>
String.fromCodePoint(byte)
).join("");
return btoa(binString);
}
function fromBase64(base64: string): string {
const binString = atob(base64);
const bytes = Uint8Array.from(binString, (c) =>
c.codePointAt(0)!
);
return new TextDecoder().decode(bytes);
}
// Works with any Unicode string
toBase64("Hello 🌍"); // "SGVsbG8g8J+MjQ=="
fromBase64("SGVsbG8g8J+MjQ=="); // "Hello 🌍"Node.js Buffer API
In Node.js, the Buffer class provides a cleaner API for Base64 operations and handles Unicode natively:
// Encoding
const encoded = Buffer.from("Hello 🌍", "utf-8").toString("base64");
// "SGVsbG8g8J+MjQ=="
// Decoding
const decoded = Buffer.from("SGVsbG8g8J+MjQ==", "base64").toString("utf-8");
// "Hello 🌍"
// Encoding binary data (e.g., reading a file)
import { readFileSync } from "fs";
const fileBase64 = readFileSync("image.png").toString("base64");Web APIs: FileReader and Fetch
When working with files in the browser, you can use the FileReader API or the Fetch API to convert files to Base64:
// Using FileReader
function fileToBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const result = reader.result as string;
resolve(result.split(",")[1]); // Remove data URI prefix
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// Using Fetch API (for URLs)
async function urlToBase64(url: string): Promise<string> {
const response = await fetch(url);
const blob = await response.blob();
const buffer = await blob.arrayBuffer();
const bytes = new Uint8Array(buffer);
const binString = Array.from(bytes, (b) =>
String.fromCodePoint(b)
).join("");
return btoa(binString);
}Base64URL vs Standard Base64
Standard Base64 uses + and / as its 62nd and 63rd characters, plus = for padding. This is problematic in URLs because all three characters have special meaning in the URL specification.
| Feature | Standard Base64 | Base64URL |
|---|---|---|
| Character 62 | + | - |
| Character 63 | / | _ |
| Padding | = | Omitted |
| URL safe | No | Yes |
Base64URL (defined in RFC 4648) replaces + with - and / with _, and omits the padding = characters. This makes the output safe for use in URLs, filenames, and query parameters without any additional encoding.
// Converting between Standard Base64 and Base64URL
function toBase64Url(base64: string): string {
return base64
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
function fromBase64Url(base64url: string): string {
let base64 = base64url
.replace(/-/g, "+")
.replace(/_/g, "/");
// Restore padding
while (base64.length % 4 !== 0) {
base64 += "=";
}
return base64;
}Base64URL is used extensively in JWTs, OAuth tokens, and anywhere Base64 data needs to appear in URLs or HTTP headers safely.
Common Misconceptions About Base64
Base64 is one of the most misunderstood encodings in software development. Here are the most common myths debunked.
Myth 1: "Base64 is Encryption"
Base64 is not encryption. Encryption requires a key and is designed to make data unreadable without that key. Base64 is a fully reversible, deterministic encoding with no key involved. Anyone can decode a Base64 string instantly using freely available tools. Never use Base64 to "protect" sensitive data like passwords or API keys.
Myth 2: "Base64 Makes Data Smaller"
The opposite is true. Base64 encoding always increases the data size by approximately 33%. Every 3 bytes of input become 4 bytes of output. If you need to reduce data size, use compression (gzip, brotli) instead. In fact, Base64-encoding already-compressed data partially negates the compression benefit.
Myth 3: "Base64 is Secure for Storing Passwords"
Base64 provides zero security. If you see a password or secret stored as Base64 in a configuration file, it is as exposed as if it were in plain text. For passwords, use proper hashing algorithms (bcrypt, scrypt, Argon2). For secrets in transit, use TLS/HTTPS. For secrets at rest, use proper encryption (AES-256-GCM or similar).
Remember: Base64 is an encoding, not a security mechanism. It transforms data for compatibility, not for confidentiality. If you need security, use encryption. If you need to verify integrity, use hashing. Base64 does neither.
Understanding the Size Overhead
Base64 encoding always increases the size of your data. Understanding why — and by how much — is important for making informed decisions about when to use it.
The Math Behind 33% Overhead
The overhead comes directly from the encoding ratio. Each group of 3 input bytes (24 bits) is represented as 4 Base64 characters (also 24 bits of information, but each character occupies a full byte in storage). That means:
- 3 input bytes become 4 output bytes
- The ratio is 4/3 = 1.333... — a 33.3% increase
- A 1 MB file becomes approximately 1.33 MB when Base64-encoded
- A 10 KB image becomes approximately 13.3 KB as a Data URI
With padding and line breaks (some implementations add line breaks every 76 characters as per MIME), the actual overhead can be slightly higher.
When the Overhead Matters
- Large files: Base64-encoding a 5 MB image for a JSON API response adds ~1.7 MB of unnecessary overhead. Use multipart uploads or direct binary transfer instead.
- High-traffic APIs: If your API serves millions of requests, the 33% overhead on Base64 payloads translates directly to increased bandwidth costs.
- Mobile apps: On metered connections, the extra data transfer impacts both loading time and data usage.
- CSS Data URIs: Embedding large images as Data URIs bloats the CSS file, preventing both the CSS and the image from being cached independently.
| Original Size | Base64 Size | Overhead |
|---|---|---|
| 1 KB | 1.33 KB | +0.33 KB |
| 10 KB | 13.3 KB | +3.3 KB |
| 100 KB | 133 KB | +33 KB |
| 1 MB | 1.33 MB | +0.33 MB |
Alternatives to Base64
Base64 is not always the best choice. Here are alternative approaches and when each one makes more sense.
Hexadecimal (Base16) Encoding
Hex encoding represents each byte as two hexadecimal characters (0-9, A-F). It has a 100% size overhead (doubles the data), making it less efficient than Base64. However, it is simpler to read and debug. Hex is commonly used for displaying hash values, color codes, and small binary values where human readability matters more than compactness.
// "Hi!" in different encodings // Original bytes: [72, 105, 33] // Hex: "486921" (6 chars — 100% overhead) // Base64: "SGkh" (4 chars — 33% overhead) // Binary: "010010000110100100100001" (24 chars)
Binary Protocols
When you control both the sender and receiver, binary protocols eliminate encoding overhead entirely:
- Protocol Buffers (protobuf): Google's binary serialization format. Smaller and faster than JSON with Base64.
- MessagePack: A binary alternative to JSON that natively supports binary data without Base64 encoding.
- CBOR: Concise Binary Object Representation, used in IoT and WebAuthn.
- FlatBuffers: Zero-copy deserialization for high-performance applications.
Multipart Form Data
For file uploads over HTTP, use multipart/form-data instead of Base64-encoding the file in a JSON body. Multipart encoding transmits binary data directly without the 33% overhead:
// Instead of this (Base64 in JSON):
fetch("/api/upload", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
file: btoa(binaryData), // 33% larger!
filename: "photo.jpg"
})
});
// Prefer this (multipart form data):
const formData = new FormData();
formData.append("file", fileBlob, "photo.jpg");
fetch("/api/upload", {
method: "POST",
body: formData // Binary data sent directly
});When to Choose Each Approach
| Scenario | Recommended Approach |
|---|---|
| Small inline images (<4 KB) | Base64 Data URI |
| File uploads | Multipart form data |
| Binary in JSON (small) | Base64 encoding |
| Binary in JSON (large) | Separate binary endpoint |
| High-performance APIs | Protobuf / MessagePack |
| Debugging / logging | Hex encoding |
| URLs / tokens | Base64URL |
Try It Yourself
Now that you understand how Base64 encoding works under the hood, put your knowledge into practice. Try encoding and decoding different strings, observe the padding behavior with various input lengths, and experiment with Unicode characters to see how the output changes.
Base64 Encoder & Decoder
Encode and decode Base64 strings instantly with our free online tool. Full UTF-8 support included.
Open Base64 Tool