You usually meet the split when a JWT segment, a WebAuthn id or a PKCE challenge refuses to decode in a tool that expects MIME Base64. Flip the URL-safe toggle on the Base64 tool to switch alphabets in your browser; nothing is uploaded. The rest of this page is the conversion, the padding math, and a three-byte example.
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.