How to Encode and Decode Base64 — With Real Examples
Base64 turns binary into text so it can travel through JSON, URLs, and headers. Learn to encode and decode Base64 with real browser, CLI, and code examples.
How to Encode and Decode Base64 — With Real Examples
Base64 keeps showing up in places where binary data has to travel over text-only channels: email attachments, data URIs, JWT headers, HTTP Basic Auth. If you've ever stared at a wall of letters ending in == and wondered what it meant, this guide walks through what Base64 actually is, how to encode and decode it in the browser, terminal, and code, and where it belongs in a real project.
What Base64 encoding actually does
Base64 is a way of representing arbitrary binary data using only 64 printable ASCII characters. It's defined in RFC 4648 and has been around since the early days of email, where message bodies were restricted to 7-bit ASCII. Anything with a non-printable byte — an image, a compressed file, a cryptographic key — would get mangled by old mail relays. Base64 was the workaround, and it stuck.
The 64-character alphabet
The standard alphabet is the uppercase letters A-Z, the lowercase letters a-z, the digits 0-9, and the two symbols + and /. That's 62 + 2 = 64 printable characters. A 65th character, =, is used for padding. Together they represent all possible 6-bit values (0 through 63).
The URL-safe variant from RFC 4648 §5 swaps + for - and / for _, so encoded strings can live in URLs, filenames, and HTTP headers without escaping.
How the encoding works, bit by bit
Base64 takes every 3 input bytes (24 bits) and slices them into 4 groups of 6 bits. Each 6-bit value — between 0 and 63 — becomes one character from the alphabet.
Here's what happens to the 3-byte input Man:
Input: M a n
ASCII: 77 97 110
Binary: 01001101 01100001 01101110
Re-group (6-bit): 010011 010110 000101 101110
Decimal: 19 22 5 46
Base64 char: T W F u
Output: "TWFu"
When the input length isn't a multiple of 3, the encoder pads with zero bits and appends = signs so the output stays aligned to 4 characters. M alone encodes to TQ==, and Ma encodes to TWE=.
Base64 is not encryption
It's worth repeating because the mistake is so common: Base64 is reversible by anyone, with no key. Treat it as a transport format, nothing more. If you need to protect data, use real encryption. If you need to verify integrity, use a hash — the iKit Hash Generator produces SHA-256 and SHA-512 digests client-side.
Encoding and decoding Base64 in the browser
Every modern browser ships with two global functions: btoa (binary to ASCII, i.e. encode) and atob (ASCII to binary, i.e. decode). They work for plain ASCII text without ceremony:
const encoded = btoa("Hello, world!");
console.log(encoded);
// "SGVsbG8sIHdvcmxkIQ=="
const decoded = atob(encoded);
console.log(decoded);
// "Hello, world!"
The Unicode trap
btoa only accepts characters in the Latin-1 range (code points 0-255). Pass in anything multi-byte and you'll get InvalidCharacterError: The string to be encoded contains characters outside of the Latin1 range. To encode arbitrary Unicode safely, go through TextEncoder and treat the bytes yourself:
function encodeUtf8Base64(str) {
const bytes = new TextEncoder().encode(str);
let binary = "";
bytes.forEach(b => (binary += String.fromCharCode(b)));
return btoa(binary);
}
function decodeUtf8Base64(b64) {
const binary = atob(b64);
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
return new TextDecoder().decode(bytes);
}
encodeUtf8Base64("こんにちは");
// "44GT44KT44Gr44Gh44Gv"
The MDN reference on Base64 covers the edge cases in more detail, including the newer Uint8Array.fromBase64() method rolling out across browsers.
When you don't want to write the code
If you just need to paste a string and see the encoded (or decoded) version, skip the DevTools gymnastics and use the iKit Base64 Encoder. It runs entirely client-side — the bytes never leave your browser — and handles Unicode, URL-safe variants, and file uploads out of the box.
Encoding and decoding Base64 on the command line
The terminal is often the fastest path for one-off conversions, and it's scriptable enough to drop into a CI pipeline.
macOS and Linux
GNU coreutils ships a base64 command on almost every Unix system:
# Encode a string
echo -n "Hello, world!" | base64
# SGVsbG8sIHdvcmxkIQ==
# Decode a string
echo "SGVsbG8sIHdvcmxkIQ==" | base64 --decode
# Hello, world!
# Encode a file (useful for embedding images inline)
base64 < logo.png > logo.b64
# Decode back to the original binary file
base64 --decode < logo.b64 > logo.png
Watch out for the echo -n — without -n, echo appends a newline that gets encoded along with your input, producing the wrong output.
Windows PowerShell
PowerShell doesn't have a dedicated base64 cmdlet, but .NET's Convert class does the job:
# Encode
$bytes = [System.Text.Encoding]::UTF8.GetBytes("Hello, world!")
[Convert]::ToBase64String($bytes)
# SGVsbG8sIHdvcmxkIQ==
# Decode
[System.Text.Encoding]::UTF8.GetString(
[Convert]::FromBase64String("SGVsbG8sIHdvcmxkIQ==")
)
# Hello, world!
OpenSSL as a portable fallback
If you're on a stripped-down container without base64 in PATH, OpenSSL is almost always installed and speaks Base64 natively:
echo -n "Hello, world!" | openssl base64
# SGVsbG8sIHdvcmxkIQ==
echo "SGVsbG8sIHdvcmxkIQ==" | openssl base64 -d
# Hello, world!
Encoding and decoding Base64 in code
Most languages expose Base64 in their standard library. Here's the minimum you need in the three most common back-end ecosystems.
Python
The base64 module in the standard library is your go-to. It returns bytes, so you'll typically decode to a UTF-8 string at the end:
import base64
# Standard Base64
base64.b64encode(b"Hello, world!").decode()
# 'SGVsbG8sIHdvcmxkIQ=='
base64.b64decode("SGVsbG8sIHdvcmxkIQ==").decode()
# 'Hello, world!'
# URL-safe variant, used by JWT and most OAuth flows
base64.urlsafe_b64encode(b"<<>>??").decode()
# 'PDw-Pj8_'
Node.js
Node's Buffer handles both directions without importing anything:
// Encode
Buffer.from("Hello, world!").toString("base64");
// 'SGVsbG8sIHdvcmxkIQ=='
// Decode
Buffer.from("SGVsbG8sIHdvcmxkIQ==", "base64").toString("utf8");
// 'Hello, world!'
// URL-safe (manually stripped)
Buffer.from("Hello, world!")
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
PHP
PHP has one-line functions built into core:
echo base64_encode("Hello, world!");
// SGVsbG8sIHdvcmxkIQ==
echo base64_decode("SGVsbG8sIHdvcmxkIQ==");
// Hello, world!
Five real-world examples of Base64
Theoretical encoding is fine; knowing where you'll actually see it matters more. Here are the five most common places.
1. Data URIs for inline images and fonts
Data URIs let you embed an image directly in HTML or CSS, trading an HTTP request for a larger payload. The format is data:<mime>;base64,<encoded>:
<img alt="1×1 red pixel"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/AAAZ4gk3AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=" />
Useful for tiny assets (<2 KB), favicons, and email templates where external hosting is unreliable. For larger images, compress them first and host them normally; see the iKit Image Compressor for lossless PNG and WebP compression.
2. HTTP Basic Authentication
The Authorization: Basic header is Base64 of username:password. It's not encrypted — it's only obscured — which is why Basic Auth only belongs on HTTPS connections:
Authorization: Basic dXNlcjpwYXNzd29yZA==
Decode with base64 -d and you're back to user:password. If you need to generate random, SSL-safe API keys instead of hand-typing passwords, the iKit Password Generator creates them client-side using crypto.getRandomValues.
3. JWT tokens
A JSON Web Token is three URL-safe Base64 segments separated by dots: header.payload.signature. The first two segments are plain JSON, Base64-encoded without padding:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIn0
.
QTbSftRTuZ1PZTxhmvAMcXYxk5P7yoYTtv2T7_LlOYg
Decode the first segment with base64 -d (after adding back any missing = padding) and you get {"alg":"HS256","typ":"JWT"}. The signature is the only part that actually protects the token — the other two are fully readable. This is why you should never put secrets in a JWT payload.
4. Email attachments (MIME)
Every attachment you send over SMTP is Base64-encoded. Mail servers originally only accepted 7-bit ASCII, and that legacy lives on. If you view the raw source of an email with an attached PDF, you'll see something like:
Content-Type: application/pdf; name="invoice.pdf"
Content-Transfer-Encoding: base64
JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9GaWx0ZXIvRmxhdGVEZWNvZGUv
TGVuZ3RoIDE1Nj4+c3RyZWFtCnic...
The lines wrap at 76 characters per RFC 2045 convention, which is why MIME-encoded blocks look like narrow columns rather than one long string.
5. PEM-encoded certificates and keys
TLS certificates, SSH keys, and most cryptographic material ship as PEM — a human-readable wrapper around Base64-encoded DER binary. You've seen them before:
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEagZDbTANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJV
UzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEV
...
-----END CERTIFICATE-----
Strip the header, footer, and newlines; decode the remainder; and you get the DER-encoded X.509 structure. Tools like openssl x509 -text -in cert.pem do that automatically.
Common mistakes to avoid
A few patterns trip up developers again and again.
| Mistake | Why it fails | Fix |
|---|---|---|
| Treating Base64 as a secret | Anyone can decode it in one command | Use AES-GCM or libsodium for secrets |
| Using standard alphabet in URLs | + and / get URL-encoded and break |
Use URL-safe Base64 (- and _) |
| Forgetting padding when concatenating | Decoders misalign on 4-byte groups | Strip padding before joining, re-pad before decoding |
btoa on Unicode strings |
Throws on characters above code point 255 | Go through TextEncoder first |
Encoding echo "text" in bash |
Includes a trailing newline | Use echo -n or printf |
When Base64 is the wrong choice
Base64 makes sense when you genuinely need to embed binary in a text-only medium. If you're shipping data between two services that both speak binary (gRPC, Protobuf, MessagePack), skipping Base64 is a 33% size win and a non-trivial CPU saving at scale. Reach for it by default only when one of these is true:
- The transport is text-only (email, JSON over HTTP, URLs, YAML).
- The payload is small (< a few hundred KB).
- Human readability or copy-pasting matters.
- You're matching an existing standard that mandates it (JWT, PEM, MIME, Basic Auth).
Padding and line-wrapping gotchas
Some producers strip = padding; some wrap lines at 64 or 76 characters; some do neither. Before decoding, normalise the input:
import base64
def safe_b64decode(s: str) -> bytes:
# Remove whitespace/newlines and re-add missing padding
s = "".join(s.split())
missing = -len(s) % 4
return base64.b64decode(s + "=" * missing)
safe_b64decode("SGVsbG8sIHdvcmxkIQ") # works without padding
# b'Hello, world!'
Most libraries are strict by default — add defensive padding at your ingestion layer if you're consuming Base64 from third parties.
A quick mental checklist
Before you reach for Base64, ask these five questions. If you answer "yes" to any, it's probably the right choice:
- Does this data need to pass through a text-only channel (email, JSON, URL, header)?
- Is the payload small enough that a 33% size inflation is acceptable?
- Do I need human-readable or copy-pasteable encoding?
- Am I implementing a spec that requires Base64 (JWT, PEM, MIME, Basic Auth, Data URI)?
- Do I need to embed binary directly in source code or config?
If none of those apply, you're probably better off with raw bytes, gzip, or a proper binary serialization format.
Related on iKit
- Format ugly JSON in 3 ways — Most Base64-decoded payloads are JSON. Format them next so the structure is readable instead of a one-line wall.
- Generate a UUID v4 in any language — JWT tokens carry both Base64-encoded segments and a UUID-style
jti— same crypto building blocks, different roles. - Convert SQL dumps to Excel in 30 seconds — MySQL exports often hold Base64-wrapped BLOBs alongside regular columns — this walks you through getting them into a clean .xlsx.
Related posts
Apple Touch Icon Generator: Sizes, Specs & Setup (2026)
Apple touch icon generator that builds every iOS, iPadOS, and Safari pinned-tab size from one source — no upload, no sign-up, on-brand in 30 seconds.
Android Mipmap Generator: 5 Density Folders, One Click (2026)
Generate every Android mipmap density (mdpi to xxxhdpi) from one source PNG — no Android Studio, no upload, ready as a drop-in res/ folder in seconds.
Crop PDF Online: Trim White Margins (No Upload, 2026)
Trim white margins from any PDF in your browser — no upload, no watermark, no sign-up. Here is exactly how PDF cropping works in 2026.