Base64 turns arbitrary binary data into text using 64 characters that survive any transport: A–Z, a–z, 0–9, + and /, with = as padding.
How it works
Take three bytes - 24 bits. Split them into four groups of six bits. Each 6-bit group indexes into the 64-character alphabet.
Input: M a n
Bits: 01001101 01100001 01101110
Regroup: 010011 010110 000101 101110
Output: T W F u
Three bytes in, four characters out. That ratio is the whole story: base64 is always 33% larger than the input, plus padding.
When the input length is not a multiple of three, the last group is padded with = so the output length stays a multiple of four.
base64 vs base64url
The standard alphabet uses + and /, both of which have meaning in URLs - + can be read as a space, / as a path separator.
base64url swaps them: + becomes -, / becomes _, and the trailing = padding is usually dropped entirely.
This is why decoding a JWT with a plain base64 decoder sometimes fails. JWTs use base64url. The fix is two replacements and re-adding padding:
const decode = (segment) => {
const padded = segment.replace(/-/g, "+").replace(/_/g, "/");
return atob(padded + "=".repeat((4 - (padded.length % 4)) % 4));
};
The UTF-8 trap in JavaScript
btoa and atob operate on binary strings, one byte per character. Give btoa anything outside Latin-1 and it throws; atob on multi-byte content returns mojibake.
Correct round trip:
const encode = (text) =>
btoa(String.fromCharCode(...new TextEncoder().encode(text)));
const decode = (b64) =>
new TextDecoder().decode(
Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)),
);
Skip the TextEncoder step and every accented character, emoji and non-Latin script breaks.
Base64 is not encryption
It provides zero confidentiality. Anyone can decode it in one line, and every browser has a decoder built in.
Things it does not do: hide an API key in your bundle, protect a config value, or make a token safe to log. If you are reaching for base64 to conceal something, you want encryption instead.
When inlining is worth it
Base64 in a data URI removes a network request at the cost of 33% more bytes and no caching.
Worth it: tiny assets under about 2KB - a small icon, a placeholder blur - where a round trip costs more than the extra bytes.
Not worth it: anything larger. A 100KB image becomes 133KB, cannot be cached separately, blocks the CSS or HTML it is embedded in from being parsed, and has to be re-downloaded whenever that file changes.
The number that decides it is round-trip latency versus transferred bytes. On HTTP/2 with many parallel requests, the round-trip cost collapsed and the case for inlining got much weaker than it was a decade ago.
If you are base64-encoding something to put in a JSON payload, check whether you can send it as a separate binary upload instead. You are paying a third more bandwidth for the convenience of one request.
Launch it where the numbers are checked
RankCert ranks products on domain control we verify ourselves. Listing is free and the link stays dofollow whether or not you display the badge.
