The two-character difference
| Standard Base64 (RFC 4648 §4) | Base64 URL (RFC 4648 §5) | |
|---|---|---|
| Index 62 | + | - |
| Index 63 | / | _ |
| Padding | = required to multiple of 4 | Usually omitted |
| Indices 0–61 | A–Z a–z 0–9 | Same |
Convert by hand
Standard → URL:
standard
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, ""); URL → Standard (re-pad before decoding):
const padded = url
.replace(/-/g, "+")
.replace(/_/g, "/");
const repadded = padded + "=".repeat((4 - padded.length % 4) % 4); Where you'll see it
- JWTs. All three segments are Base64 URL. See our JWT decoder — it re-pads automatically.
- Web Push subscriptions. The
p256dhandauthkeys arrive as Base64 URL. - OAuth PKCE. The
code_verifierand its SHA-256code_challengeare Base64 URL with padding stripped. - Magic links. Anywhere a token lives in a query string. Standard Base64 + URL-encoding doubles the bytes and adds a layer of decoding bugs.
- S3 / object-storage keys. When you want a
deterministic, opaque key from a hash and the storage system
treats
/as a path separator.
Common mistakes
Forgetting to re-pad before decoding
Standard Base64 decoders require padding. If you call
atob() directly on a Base64 URL string of length 22
(e.g., a JWT segment), you'll get InvalidCharacterError.
Either re-pad (above) or use a library that accepts unpadded input.
Treating it as URL-encoded Base64
Don't encodeURIComponent a Base64 URL string — it's
already URL-safe. Doing so produces things like
%3D%3D from a literal ==, and now you
have to decode twice on the other end.
Mixing alphabets
Generating with btoa (standard) and decoding with a
Base64-URL-only function will silently fail on values containing
a + or /. Pick one alphabet at the
boundary and stick with it.
Native support across languages
| Language | Standard | Base64 URL |
|---|---|---|
| JavaScript (browser) | btoa / atob | None — convert manually or use a lib |
| Node.js | Buffer.from(s, "base64") | Buffer.from(s, "base64url") (Node 16+) |
| Python | base64.b64encode/decode | base64.urlsafe_b64encode/decode |
| Go | base64.StdEncoding | base64.URLEncoding / RawURLEncoding |
| Java | Base64.getEncoder() | Base64.getUrlEncoder().withoutPadding() |
Rust (base64) | STANDARD | URL_SAFE_NO_PAD |
Reference
- RFC 4648 §5 — Base64 URL alphabet.
- RFC 7515 Appendix C — JWS Base64 URL handling, including unpadded form.
- Base64 vs Base64 URL — fuller comparison
- JWT decoder (uses Base64 URL throughout)
FAQ
Is Base64 URL the same as Base64?
Same encoding (6-bit groups → ASCII), different alphabet. Standard Base64 uses A-Z a-z 0-9 + /. Base64 URL substitutes - for + and _ for /, both of which are URL- and filename-safe. Padding (=) is usually omitted because it conflicts with query-string syntax.
Why do JWTs use Base64 URL?
JWTs travel in URLs, headers, and cookies — places where + and / have other meanings. The standards (RFC 7515 / 7519) mandate Base64 URL, no padding, throughout the format.
Can I just URL-encode standard Base64?
You can, but you'll pay 4 bytes per /, double-decode on the receiver, and run into double-encoding bugs when something else URL-encodes the result again. Base64 URL is the simpler answer and it's what other systems expect.
What about the padding?
Standard Base64 pads to a multiple of 4 with =. Base64 URL almost always strips them. To re-add: pad with = until the length is a multiple of 4. Some libraries fail without padding; the converter below re-pads automatically before decoding.
Is Base64 URL the same as 'web-safe Base64'?
Yes — different name for the same thing. RFC 4648 §5 defines it formally as 'base64url'. 'URL-safe Base64' and 'web-safe Base64' are common informal names.