How the encoding works
Base64 takes three input bytes — 24 bits — and re-splits them into four 6-bit groups, each of which indexes a 64-character alphabet: A–Z, a–z, 0–9, +, /. Three bytes in, four characters out, which is why Base64 output is always about 33% larger than its input. When the input length is not a multiple of three, the final group is padded with one or two = characters so that the total length stays a multiple of four.
That expansion is the cost of the guarantee. Every output character is in the ASCII printable range, contains no whitespace, no control characters, and nothing a mail transfer agent or an XML parser will mangle. It is a transport encoding, not compression and emphatically not encryption — the alphabet is public and decoding needs no key.
Standard vs URL-safe (base64url)
The standard alphabet's last two characters, + and /, are both meaningful inside a URL — + means a space in a query string and / is a path separator. RFC 4648 §5 therefore defines base64url, which substitutes - and _ and usually drops the = padding, since = also needs escaping. The underlying bytes are identical; only the rendering differs.
Read the full explanation of base64 encode / decode results ↓Show less ↑
| Standard (RFC 4648 §4) | URL-safe (§5) | |
|---|---|---|
| Index 62 | + | - |
| Index 63 | / | _ |
| Padding | = required | Usually omitted |
| Used by | MIME, HTTP Basic, PEM, Kubernetes Secrets | JWT, OAuth PKCE, WebAuthn, URL parameters |
This tool detects - or _ in input you are decoding and switches alphabets automatically, and it restores missing padding before decoding. That is why a JWT segment pasted here decodes without you having to fix it up first — though the JWT decoder will split the token and format the claims for you as well.
The Unicode problem most Base64 tools get wrong
JavaScript's built-in btoa() operates on a "binary string" — one character per byte — and throws InvalidCharacterError on any code point above U+00FF. Pass it café and it works; pass it 日本語 or an emoji and it fails outright. The workaround that circulated for years, btoa(unescape(encodeURIComponent(str))), works but relies on unescape(), which has been deprecated for a decade.
The correct approach, and the one used here, is to convert explicitly: new TextEncoder().encode(str) produces the UTF-8 bytes, and those bytes are then Base64-encoded. Decoding reverses it with TextDecoder. This matters practically — if you encode text here and decode it in Python with base64.b64decode(s).decode("utf-8"), you get exactly the original string back, because both sides agree that the underlying bytes are UTF-8.
function encodeBase64(text) {
const bytes = new TextEncoder().encode(text)
let binary = ''
for (const b of bytes) binary += String.fromCharCode(b)
return btoa(binary)
}
function decodeBase64(b64) {
const binary = atob(b64)
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0))
// fatal: true throws on invalid UTF-8 instead of inserting U+FFFD
return new TextDecoder('utf-8', { fatal: true }).decode(bytes)
}
// URL-safe variants
const toUrlSafe = (s) => s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
const fromUrlSafe = (s) => s.replace(/-/g, '+').replace(/_/g, '/')The fatal: true option is the difference between a tool that tells you the truth and one that does not. Without it, TextDecoder replaces invalid byte sequences with U+FFFD (�) and returns a string, so decoding a PNG produces plausible-looking garbage. With it, you get an exception, which this page turns into a message telling you the data decoded fine but is binary rather than text.
Command-line equivalents
# Encode — note printf, not echo, so no trailing newline is included
printf 'hello world' | base64
# -> aGVsbG8gd29ybGQ=
# Decode (GNU coreutils uses -d, macOS/BSD accepts -D or -d)
echo 'aGVsbG8gd29ybGQ=' | base64 -d
# openssl works everywhere; -A keeps it on one line
printf 'hello world' | openssl base64 -A
echo 'aGVsbG8gd29ybGQ=' | openssl base64 -d -A
# Long output wraps at 76 chars by default — disable it for a single line
base64 -w 0 certificate.der # GNU
base64 -i certificate.der | tr -d '\n' # macOS
# Read a Kubernetes secret (stored Base64, not encrypted)
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d
# Decode a JWT payload — base64url needs the alphabet translated first
echo "$JWT" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/nullThe last line is worth keeping. tr '_-' '/+' converts base64url back to the standard alphabet, and the 2>/dev/null suppresses the padding complaint that some base64 builds emit when the input length is not a multiple of four.
Reading errors from this tool
- "Not valid Base64." The input contains characters outside the alphabet. Usually a copy-paste that picked up a quotation mark, an ellipsis from truncated output, or a stray URL fragment. Whitespace and line breaks are stripped automatically, so they are never the cause.
- "Invalid Base64 length." A Base64 string can never be one more than a multiple of four characters, because 6 bits cannot encode a whole byte. This means the value was truncated — a common outcome of copying from a terminal that wrapped, or from a database column that was too narrow.
- "Decoded successfully but the bytes are not valid UTF-8." The decode worked; the result is binary. A PNG, a gzip stream, a DER-encoded certificate, or an encrypted blob all produce this. It is not an error in your input.
- Output looks like `` or `é`. Double encoding, or a UTF-8/Latin-1 mismatch upstream.
éis the two UTF-8 bytes oféinterpreted as Latin-1 — the data was decoded once with the wrong charset before it reached you.
Where Base64 shows up in DNS and mail work
Base64 is everywhere in the infrastructure this site is about. DKIM public keys are Base64 inside a p= tag in a TXT record — an oversized RSA-2048 key is exactly why DKIM records so often need splitting across multiple 255-character strings; check yours with the DKIM checker. DNSSEC DNSKEY and RRSIG records carry Base64 key and signature material. TLSA and SSHFP use hex rather than Base64, which is a frequent source of paste errors between them.
PEM files are Base64 with a header and footer: -----BEGIN CERTIFICATE-----, 64-character lines of Base64-encoded DER, then the matching END line. If you strip the delimiters and the line breaks, what remains decodes here — though the result is binary DER, so you will get the "not valid UTF-8" message, which is the correct answer. Use openssl x509 -text to read it properly, or the SSL certificate checker to inspect what a live server presents.
A final practical note on size. Because Base64 inflates data by a third, embedding a large image as a data: URI costs you meaningfully more bytes over the wire than linking it, and it defeats caching because the image can no longer be cached separately from the document. It is a reasonable trick for icons of a few hundred bytes and a poor one for photographs.