β Blog Β· May 23, 2026 Β· dev, encoding
Base64 vs URL-Safe Base64: When the Difference Matters
Standard Base64 (RFC 4648 section 4) uses 64 characters: A-Z, a-z,0-9, plus + and /. URL-safe Base64 (RFC 4648 section 5) swaps the last two for - and _, and usually drops the trailing= padding. That two-character difference is the entire reason JWTs, OAuth tokens, and image URLs work β and the entire reason naive decoders break on them.
The numbers
Base64 inflates payload size by exactly 4/3 β three bytes in become four characters out. A 1 KB image becomes ~1366 characters. The output length is always a multiple of 4 in standard Base64 (padded with =). In URL-safe Base64 the padding is typically omitted, so the length is whatever it works out to: a 32-byte SHA-256 hash becomes 43 characters URL-safe, or 44 standard.
The two alphabets side by side
| Position | Standard (RFC 4648 Β§4) | URL-safe (Β§5) |
|---|---|---|
| 0β25 | AβZ | AβZ |
| 26β51 | aβz | aβz |
| 52β61 | 0β9 | 0β9 |
| 62 | + | - |
| 63 | / | _ |
| Padding | = | often omitted |
Why URL-safe exists
Put a standard Base64 string into a URL and the / looks like a path delimiter, the + means space in form-urlencoded query strings, and the= is reserved. The string aGVsbG8= in a URL becomesaGVsbG8%3D after percent-encoding β longer, uglier, and a decoder that doesn't un-percent-encode first will fail. URL-safe Base64 avoids all three reserved characters and the padding, so the string drops into a URL untouched.
The same bytes, two encodings
Input bytes (hex): FB EF FF
Standard Base64: ++//
URL-safe Base64: --__
Input: { "alg": "HS256" } (16 bytes)
Standard: eyAiYWxnIjogIkhTMjU2IiB9
URL-safe: eyAiYWxnIjogIkhTMjU2IiB9 (same β no + / present)Notice β most short ASCII strings encode identically in both alphabets, because they never produce a + or /. The difference only appears when the input has certain byte patterns. That is why bugs hide: your test data works in both modes, then a binary payload arrives and one decoder explodes.
JWT uses URL-safe Base64. Always.
Every JWT is three URL-safe Base64 strings joined by . dots. RFC 7515 mandates the URL-safe variant with no padding β section 2 says it explicitly. If your decoder is rejecting JWTs from a third party, the cause is almost always one of:
- Standard Base64 decoder used on URL-safe input (chokes on
-or_). - Decoder expects padding and the token has none.
- The token was double-encoded somewhere in transit.
Paste any JWT into our JWT decoder to see the three URL-safe Base64 parts decoded to JSON.
Converting between the two variants
// Standard β URL-safe
function toUrlSafe(b64: string): string {
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
// URL-safe β Standard (re-pad to multiple of 4)
function fromUrlSafe(b64url: string): string {
let s = b64url.replace(/-/g, '+').replace(/_/g, '/');
while (s.length % 4) s += '=';
return s;
}
// In modern Node, prefer the native encodings
Buffer.from(data, 'base64url').toString('utf8');
Buffer.from('hello').toString('base64url');Decision: which one should you emit?
| You are putting Base64 into⦠| Use |
|---|---|
| A URL path or query string | URL-safe, no padding |
| A JWT, OAuth token, magic link | URL-safe (mandated) |
| An HTTP header value | URL-safe (avoids accidental space-to-plus) |
| An email body, MIME attachment | Standard with padding (RFC 2045) |
| A JSON field | Either works β pick one and document it |
| A data: URI for an image | Standard (per RFC 2397) |
| A filename | URL-safe β / in a filename is a different file |
The padding question
Padding (=) exists so that a Base64 string can be concatenated and still be decodable in 4-character chunks. For one-shot encoding it is decorative. You can strip it when emitting and re-pad on decode by appending = until length is a multiple of 4. Most URL-safe contexts omit it. Most MIME contexts require it.
One more gotcha: line wrapping
RFC 2045 (MIME) Base64 wraps at 76 characters. RFC 4648 Base64 does not. If you paste a MIME-style Base64 blob into a JSON parser or a URL, the newlines break it. Strip them with.replace(/\s/g, '') before decoding.
Tools
- Base64 Encoder / Decoder β toggle between standard and URL-safe
- JWT Decoder β URL-safe Base64 in action
- Image to Base64 β for data: URIs in CSS and email
- URL Encoder β when you need to escape standard Base64 in a URL
- Hash Generator β pair SHA-256 + Base64url to fingerprint anything