What is Base64 Encoding? Complete Guide with Examples & When to Use It
Base64 encoding is one of the most widely used data-encoding schemes in software development — yet it is frequently misunderstood, misused, or confused with encryption. Whether you are embedding images in HTML, decoding a JWT token, or sending binary data over a REST API, understanding Base64 is an essential skill for every developer. This comprehensive guide explains what Base64 is, exactly how the algorithm works, when you should (and should not) use it, and provides ready-to-run code examples in JavaScript, Node.js, and Python.
What is Base64 Encoding?
Base64 encoding is a binary-to-text encoding scheme that represents binary data using only 64 printable ASCII characters. The name comes directly from the number 64 — the size of its character alphabet. Its primary purpose is to allow binary data (images, files, arbitrary bytes) to be safely transported through systems that were originally designed to handle only plain text, such as email servers, HTTP headers, and JSON payloads.
Base64 was formally defined in RFC 2045 (1996) as part of the MIME (Multipurpose Internet Mail Extensions) specification, but its roots go back to the early days of the internet when email systems could only reliably transmit 7-bit ASCII text. Sending a binary file like an image directly through an email server would corrupt the data, because servers were allowed to strip the 8th bit of every byte or convert certain byte sequences (like carriage returns). Base64 solved this by converting every group of 3 bytes into 4 printable ASCII characters — characters that every system understood and would never modify.
Today Base64 is defined in RFC 4648 and appears in dozens of internet standards: HTTP Basic Authentication, JWT tokens, data URIs, PEM certificates, MIME email attachments, and more. It is not a compression algorithm, and it is not encryption — it is purely a way to represent arbitrary bytes using a safe, limited set of text characters.
How Base64 Encoding Works
The Base64 algorithm is elegantly simple. At a high level, it takes every 3 bytes (24 bits) of input and converts them into 4 Base64 characters (24 bits), each representing 6 bits of the original data. Here is the step-by-step process:
- Step 1 — Take 3 bytes: Read 3 bytes (24 bits) of binary input at a time. For example, the ASCII string
Manis bytes0x4D 0x61 0x6Ewhich in binary is01001101 01100001 01101110. - Step 2 — Split into 4 groups of 6 bits: Concatenate the 24 bits and split into four 6-bit groups:
010011 | 010110 | 000101 | 101110, giving decimal values 19, 22, 5, 46. - Step 3 — Map to Base64 characters: Use the Base64 alphabet table to map each 6-bit value to a character. 19 →
T, 22 →W, 5 →F, 46 →u. SoManencodes toTWFu. - Step 4 — Handle padding: If the input length is not a multiple of 3, the encoder pads the output with one or two
=characters to make the total output length a multiple of 4. This allows decoders to know exactly how many bytes the original data contained.
Let's trace through a complete example. Encoding the string "Hello" (5 bytes):
Input: H e l l o
Bytes: 0x48 0x65 0x6C 0x6C 0x6F
Binary: 01001000 01100101 01101100 01101100 01101111
Group 1 (bytes 1-3): 01001000 01100101 01101100
6-bit groups: 010010 | 000110 | 010101 | 101100
Indices: 18 | 6 | 21 | 44
Chars: S | G | V | s
Group 2 (bytes 4-5 + padding): 01101100 01101111 00000000 (padded)
6-bit groups: 011011 | 000110 | 111100 | (pad)
Indices: 27 | 6 | 60
Chars: b | G | 8 | =
Result: "SGVsbG8="The trailing = tells the decoder that one byte of padding was added, meaning the last group contained only 2 real bytes. Two == means only 1 real byte was in the last group.
Base64 Character Table
The standard Base64 alphabet consists of exactly 64 characters plus the = padding character:
| Index Range | Characters | Count | Description |
|---|---|---|---|
| 0 – 25 | A – Z | 26 | Uppercase Latin letters |
| 26 – 51 | a – z | 26 | Lowercase Latin letters |
| 52 – 61 | 0 – 9 | 10 | Decimal digits |
| 62 | + | 1 | Plus sign (standard Base64) |
| 63 | / | 1 | Forward slash (standard Base64) |
| Padding | = | 1 | Padding character (not part of alphabet) |
These 64 characters were chosen because they are safe ASCII characters that every legacy text-handling system would preserve without modification. Notice that characters like <, >, &, ", and whitespace are deliberately excluded because they have special meaning in HTML, XML, URLs, and email headers.
There is also a variant called Base64URL (defined in RFC 4648 §5), which replaces + with - and / with _ to make the output safe for use in URLs and filenames without percent-encoding. This is the variant used in JWT tokens and many modern web APIs.
When Should You Use Base64?
Base64 shines whenever you need to safely embed or transmit binary data through a text-only channel. Here are the most important real-world use cases:
1. Embedding Images in HTML and CSS (Data URIs)
Instead of referencing an external image file, you can embed it directly in your HTML or CSS using a data URI. This eliminates an HTTP request and is particularly useful for small icons, inline SVGs, and loading spinners.
<!-- Embedding a PNG image directly in HTML -->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..." alt="logo" />
/* Or in CSS */
.icon {
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cD...");
}2. JWT (JSON Web Tokens)
JSON Web Tokens use Base64URL encoding for all three of their parts: the header, payload, and signature. Because JWT is transmitted in HTTP headers and URLs, the URL-safe variant of Base64 (- and _ instead of + and /) is used. You can decode any JWT payload by simply Base64-decoding the middle segment.
// Example JWT (3 Base64URL-encoded parts separated by dots):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
// Decoding the payload (middle part):
atob("eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0")
// → '{"sub":"1234567890","name":"John Doe"}'3. HTTP Basic Authentication
The HTTP Authorization header for Basic Auth encodes credentials as Base64. The browser or client concatenates username:password, Base64-encodes the result, and sends it in the header. This is not encryption — it is just encoding — which is why HTTPS is required for Basic Auth.
// Encoding "admin:secret" for HTTP Basic Auth
btoa("admin:secret") // → "YWRtaW46c2VjcmV0"
// The resulting HTTP header:
Authorization: Basic YWRtaW46c2VjcmV04. Email Attachments (MIME)
The original use case for Base64 — email systems use MIME encoding to attach binary files (PDFs, images, executables) to email messages. The file is Base64-encoded and placed in the email body as text, then decoded by the recipient's email client.
5. API Credentials and Configuration Files
Many services encode API keys, certificates, and secrets as Base64 in environment variables and config files. This is common in Kubernetes secrets, CI/CD pipelines (GitHub Actions, CircleCI), and cloud provider SDKs where binary certificate data must be stored as a string.
# Kubernetes secret (values are Base64-encoded)
apiVersion: v1
kind: Secret
metadata:
name: my-secret
data:
username: YWRtaW4= # "admin" base64-encoded
password: c2VjcmV0cGFzcw== # "secretpass" base64-encoded6. Storing Binary Data in JSON / Databases
JSON does not natively support binary data. If you need to include a file, image, or cryptographic key in a JSON payload, Base64 encoding is the standard approach — used by Google Cloud APIs, AWS, and many REST APIs worldwide.
When NOT to Use Base64
Base64 is frequently misused. Here is when you should avoid it:
- Never use it for encryption or security. Base64 is completely reversible with zero secret — anyone can decode it instantly. It provides no confidentiality whatsoever. If you need to protect data, use proper encryption (AES, RSA) or hashing (bcrypt, SHA-256).
- Avoid it for large file transfers. Base64 inflates data size by approximately 33% (every 3 bytes become 4 characters). Transferring large images or files as Base64 through an API wastes bandwidth significantly compared to sending the raw binary via multipart form data or a presigned URL.
- Don't use it as a compression algorithm. Base64 makes data larger, not smaller. If storage or bandwidth is a concern, use compression (gzip, Brotli) before Base64 encoding, not instead of it.
- Avoid embedding very large images as data URIs. While data URIs are convenient for small icons, embedding large images in HTML/CSS bloats your document size, prevents browser caching of the image (since it is part of the HTML/CSS), and can hurt page load performance.
- Don't rely on it to sanitize user input. Some developers mistakenly Base64-encode user input thinking it prevents XSS or injection attacks. It does not — the data must still be properly validated and sanitized after decoding.
Base64 Encoding in JavaScript
Modern browsers expose two global functions for Base64 encoding and decoding: btoa() (binary-to-ASCII, i.e., encode) and atob() (ASCII-to-binary, i.e., decode). These work in all modern browsers and Deno, but have a limitation: they only handle Latin-1 (ISO-8859-1) characters, not arbitrary Unicode strings.
// Browser / Deno — basic encode & decode
const encoded = btoa("Hello, World!");
console.log(encoded); // "SGVsbG8sIFdvcmxkIQ=="
const decoded = atob("SGVsbG8sIFdvcmxkIQ==");
console.log(decoded); // "Hello, World!"
// ⚠️ Limitation: btoa() throws for non-Latin1 characters
// btoa("Hello 🌍") → DOMException: The string to be encoded
// contains characters outside of the Latin1 range.
// ✅ Solution: encode Unicode strings properly
function encodeUnicode(str) {
return btoa(
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) =>
String.fromCharCode(parseInt(p1, 16))
)
);
}
function decodeUnicode(str) {
return decodeURIComponent(
atob(str)
.split("")
.map((c) => "%" + c.charCodeAt(0).toString(16).padStart(2, "0"))
.join("")
);
}
encodeUnicode("Hello 🌍"); // "SGVsbG8g8J+MjQ=="
decodeUnicode("SGVsbG8g8J+MjQ=="); // "Hello 🌍"Base64 in Node.js (Buffer API)
Node.js uses the Buffer class for Base64 operations, which handles Unicode and binary data correctly without the Latin-1 limitation:
// Node.js — encoding strings
const encoded = Buffer.from("Hello, World!").toString("base64");
console.log(encoded); // "SGVsbG8sIFdvcmxkIQ=="
// Node.js — decoding
const decoded = Buffer.from("SGVsbG8sIFdvcmxkIQ==", "base64").toString("utf8");
console.log(decoded); // "Hello, World!"
// Node.js — encoding a file to Base64
const fs = require("fs");
const imageBuffer = fs.readFileSync("./logo.png");
const imageBase64 = imageBuffer.toString("base64");
const dataURI = `data:image/png;base64,${imageBase64}`;
// Node.js — Base64URL encoding (safe for URLs)
const base64url = Buffer.from("Hello, World!")
.toString("base64")
.replace(/+/g, "-")
.replace(///g, "_")
.replace(/=/g, ""); // strip padding for URL use
console.log(base64url); // "SGVsbG8sIFdvcmxkIQ"
// Node.js v16+ — native base64url support
const base64urlNative = Buffer.from("Hello, World!").toString("base64url");
console.log(base64urlNative); // "SGVsbG8sIFdvcmxkIQ"Base64 Encoding in Python
Python's standard library includes the base64 module, which provides functions for standard Base64, Base64URL, and several other variants. Note that Python's Base64 functions work on bytes objects, not plain strings, so you need to encode strings first.
import base64
# Encoding a string to Base64
text = "Hello, World!"
encoded_bytes = base64.b64encode(text.encode("utf-8"))
encoded_str = encoded_bytes.decode("utf-8")
print(encoded_str) # SGVsbG8sIFdvcmxkIQ==
# Decoding a Base64 string
decoded_bytes = base64.b64decode("SGVsbG8sIFdvcmxkIQ==")
decoded_str = decoded_bytes.decode("utf-8")
print(decoded_str) # Hello, World!
# Encoding a file to Base64
with open("logo.png", "rb") as image_file:
image_base64 = base64.b64encode(image_file.read()).decode("utf-8")
data_uri = f"data:image/png;base64,{image_base64}"
# URL-safe Base64 (Base64URL) — replaces + with - and / with _
url_safe_encoded = base64.urlsafe_b64encode(text.encode("utf-8"))
print(url_safe_encoded.decode("utf-8")) # SGVsbG8sIFdvcmxkIQ==
url_safe_decoded = base64.urlsafe_b64decode("SGVsbG8sIFdvcmxkIQ==")
print(url_safe_decoded.decode("utf-8")) # Hello, World!
# Handling missing padding (common when decoding JWT segments)
def decode_base64_url(data: str) -> bytes:
# Add back padding stripped by JWT
padding = 4 - len(data) % 4
data += "=" * (padding % 4)
return base64.urlsafe_b64decode(data)
payload = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0"
print(decode_base64_url(payload))
# b'{"sub":"1234567890","name":"John Doe"}'Base64 vs Hex Encoding: Which Should You Use?
Both Base64 and hexadecimal (hex) encoding are binary-to-text schemes, but they serve different purposes and have different trade-offs. Here is a detailed comparison:
| Property | Base64 | Hex |
|---|---|---|
| Alphabet size | 64 characters | 16 characters (0–9, a–f) |
| Bits per character | 6 bits | 4 bits |
| Output size overhead | ~33% larger than input | ~100% larger than input (2× bytes) |
| Efficiency | More compact | Less compact |
| Human readability | Low (looks like random text) | Medium (easy to parse byte-by-byte) |
| URL safety | No (+ and / must be escaped; use Base64URL) | Yes (all hex chars are URL-safe) |
| Common use cases | Images, JWT, email, binary in JSON | Hashes (MD5, SHA), color codes, checksums, debugging |
| Padding | Yes (= character) | No |
| Standard | RFC 4648 | No formal standard, universally understood |
Rule of thumb: Use Base64 when transmitting binary data through text channels (HTTP, JSON, email). Use hex when displaying cryptographic hashes, checksums, memory addresses, or any value where human readability matters. A SHA-256 hash in hex (2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824) is far more recognizable and debuggable than its Base64 equivalent.
Real-World Base64 Examples
Embedding an SVG Icon as a CSS Background
Small SVG icons can be inlined in CSS to eliminate extra network requests. This is especially useful for critical above-the-fold icons in server-side rendered applications:
/* Original SVG:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
</svg>
*/
/* Base64-encoded data URI in CSS */
.icon-layers {
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEyIDJMMiA3bDEwIDUgMTAtNS0xMC01eiIvPjwvc3ZnPg==");
width: 24px;
height: 24px;
background-size: contain;
background-repeat: no-repeat;
}Decoding a JWT Token Manually
A JWT has three parts separated by dots. The header and payload are Base64URL-encoded JSON objects. You can decode them without any library — which is useful for debugging:
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
".eyJzdWIiOiJ1c2VyXzEyMyIsIm5hbWUiOiJBbGljZSIsImlhdCI6MTcxNzI0NjQwMCwiZXhwIjoxNzE3MzMyODAwfQ" +
".SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
function decodeJwtPart(base64url) {
// Convert Base64URL → Base64
const base64 = base64url
.replace(/-/g, "+")
.replace(/_/g, "/")
.padEnd(base64url.length + (4 - (base64url.length % 4)) % 4, "=");
return JSON.parse(atob(base64));
}
const [headerB64, payloadB64] = jwt.split(".");
const header = decodeJwtPart(headerB64);
const payload = decodeJwtPart(payloadB64);
console.log(header);
// { alg: "HS256", typ: "JWT" }
console.log(payload);
// { sub: "user_123", name: "Alice", iat: 1717246400, exp: 1717332800 }Sending an Image via REST API
Many AI and vision APIs (OpenAI Vision, Google Vision AI) accept images as Base64 strings in their JSON request bodies:
// Sending an image to OpenAI Vision API
async function analyzeImage(imagePath) {
const fs = require("fs");
const imageData = fs.readFileSync(imagePath);
const base64Image = imageData.toString("base64");
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o",
messages: [
{
role: "user",
content: [
{ type: "text", text: "What is in this image?" },
{
type: "image_url",
image_url: { url: `data:image/jpeg;base64,${base64Image}` },
},
],
},
],
}),
});
return response.json();
}Frequently Asked Questions (FAQ)
Is Base64 a form of encryption?
No. Base64 is not encryption. Encryption requires a secret key and produces output that cannot be reversed without that key. Base64 is a completely reversible encoding that requires no key — anyone can decode Base64 in milliseconds using freely available tools. Never rely on Base64 to protect sensitive data. Use it only to safely transport data through text channels, and use proper encryption (AES, RSA, TLS) for security.
Why does Base64 output end with = or ==?
Base64 processes input in groups of 3 bytes and produces groups of 4 characters. When the input length is not a multiple of 3, the final group is incomplete. The = padding character fills in the remaining positions to keep the output aligned to 4-character blocks. One = means the last group had 2 bytes (producing 3 Base64 chars + 1 pad). Two == means the last group had only 1 byte (producing 2 Base64 chars + 2 pads). Padding is optional in some contexts (like Base64URL in JWTs) but required by strict decoders.
Is Base64 safe to use in URLs?
Standard Base64 is not URL-safe because the characters +, /, and = have special meaning in URLs and query strings. Placing standard Base64 in a URL without percent-encoding will break or corrupt the data. Always use Base64URL (replacing + with - and / with _) for URL contexts. In Node.js, use Buffer.toString("base64url"); in Python, use base64.urlsafe_b64encode().
What is Base64URL encoding and how does it differ from regular Base64?
Base64URL is a variant of Base64 defined in RFC 4648 §5 that is safe for use in URLs, filenames, and HTTP headers without percent-encoding. The only differences are: + is replaced by -, / is replaced by _, and the = padding is typically omitted. The encoding and decoding algorithm is otherwise identical. Base64URL is the standard used in JWT tokens, OAuth 2.0 PKCE challenges, and many modern web APIs. When you see a long string of letters, numbers, hyphens, and underscores in a URL or Authorization header, it is almost certainly Base64URL.
Conclusion
Base64 encoding is a foundational tool in a developer's toolkit. It solves a specific, well-defined problem: safely representing arbitrary binary data as printable ASCII text. Understanding when to reach for it — and equally important, when not to — will make you a more effective developer.
To recap the key takeaways from this guide:
- Base64 converts every 3 bytes of binary data into 4 printable ASCII characters, producing output ~33% larger than the original input.
- It is the standard encoding for images in data URIs, JWT tokens, HTTP Basic Authentication, MIME email attachments, Kubernetes secrets, and binary data in JSON APIs.
- Base64 is not encryption. It provides zero security — anyone can decode it immediately without any key.
- Use Base64URL (with
-and_) for URL contexts and=padding is typically dropped. - In JavaScript, use
btoa()/atob()for simple strings in browsers, orBufferin Node.js for full Unicode and binary support. - In Python, use the
base64module —b64encode()andb64decode()for standard, orurlsafe_b64encode()for URL-safe variant. - Base64 is more space-efficient than hex (33% vs 100% overhead), but hex is more human readable for cryptographic hashes and checksums.
Whether you are building a REST API, debugging JWT authentication, embedding assets in a web app, or configuring Kubernetes secrets, you will encounter Base64 constantly throughout your career as a developer. Mastering it is time well spent.
Ready to encode or decode Base64 right now? Try our free online Base64 Encoder & Decoder tool — instantly encode text, images, and files to Base64 or decode any Base64 string back to its original form. No signup required, works entirely in your browser, and supports standard Base64 and Base64URL formats.