Base64 and Base64URL are the same encoding with two characters swapped: standard Base64 uses + and /, Base64URL replaces them with - and _ and usually drops the = padding. That tiny difference exists because +, / and = all carry meaning inside URLs — and it is the reason a JWT segment sometimes refuses to decode in a standard Base64 tool. The Base64 tool handles standard Base64 locally in your browser.
Both come from RFC 4648
RFC 4648 defines both alphabets: section 4 is “base64” and section 5 is “base64url”, formally “Base 64 Encoding with URL and Filename Safe Alphabet”. Both map every 3 input bytes to 4 output characters from a 64-character set; the first 62 characters (A–Z, a–z, 0–9) are identical, only positions 62 and 63 differ:
chars 62,63 padding Base64 + / = required Base64URL - _ = usually omitted
A three-byte worked example
Encode the string Hi? — bytes 72 105 63. Base64 works on 6-bit groups, so the 24 bits regroup from three 8-bit bytes into four 6-bit values:
bytes: 01001000 01101001 00111111 H i ? 6-bit: 010010 000110 100100 111111 = 18 6 36 63 alphabet position 63 differs: Base64 → "SGk/" (63 = "/") Base64URL → "SGk_" (63 = "_")
Same bytes, one different character — and that character (/) is precisely the one a URL path would swallow. Because 3 bytes map to exactly 4 characters, this example needs no padding; padding only appears when the input length is not a multiple of 3.
The padding math
The = signs are pure length bookkeeping: input length mod 3 = 0 → no padding, mod 3 = 2 → one =, mod 3 = 1 → two ==. So "OK" (2 bytes) encodes to T0s= and "A" (1 byte) to QQ==. To restore stripped padding — the everyday chore when handling Base64URL — pad to the next multiple of 4: s + "=".repeat((4 - s.length % 4) % 4). A Base64 string whose length mod 4 equals 1 is impossible; if you see one, characters were lost in transit.
Why the swap matters
- In a URL,
+historically decodes to a space in query strings,/separates path segments, and=separates keys from values. Standard Base64 in a URL either breaks or needs percent-encoding on top — double encoding, twice the bugs. - In file names,
/is a directory separator on every OS. Base64URL output is filename-safe by construction.
Where you meet each one
Standard Base64: MIME email attachments, data: URLs, Authorization: Basic headers, X.509/PEM certificates, most APIs' binary fields. Base64URL: all three JWT segments (RFC 7519 mandates it — see how to decode a JWT), WebAuthn identifiers, OAuth PKCE code challenges, and short link tokens.
Converting between them
Because only two characters differ, conversion is two string replacements plus padding:
// Base64URL → Base64 let b64 = input.replace(/-/g, "+").replace(/_/g, "/"); b64 += "=".repeat((4 - (b64.length % 4)) % 4); // restore padding // Base64 → Base64URL input.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
The bytes never change — only their textual clothes. If a decode still fails after conversion, the string was truncated or was never Base64 to begin with; the Base64 decode page reports invalid input instead of guessing.
The one thing neither variant is
Encryption. Encoding is a reversible mapping anyone can undo without a key — its 33% size overhead buys transport safety, not secrecy. If a value must be confidential, encrypt it; if it merely must survive a URL, Base64URL is exactly the right tool.
