Comparison
Base64 vs base64url: two characters that matter
Same alphabet, different last two characters, different padding. One-line difference, totally different use cases.
By Buğra SözeriPublished
TL;DR. Base64 and base64url are the same encoding except for three details defined in RFC 4648: + becomes -, / becomes _, and padding = is dropped. Use base64url anywhere the encoded value travels in a URL, JWT, or signed cookie; use standard Base64 for email attachments, HTTP Basic Auth, and data URIs.
Both are defined in RFC 4648. Standard Base64(§4) and base64url(§5) differ in exactly three details: two alphabet substitutions and the treatment of padding. That’s it. But mixing them up breaks JWTs, signed cookies, and anything else that travels in a URL.
The diff
| Property | Standard Base64 | Base64url |
|---|---|---|
| 62nd character | + | - |
| 63rd character | / | _ |
| Padding | = to multiple of 4 | Typically omitted |
| URL-safe? | No (+, /, = need percent-encoding) | Yes |
| Defined in | RFC 4648 §4 | RFC 4648 §5 |
The same input, both encodings
Encoding https://example.com/?q=hello world:
- Standard:
aHR0cHM6Ly9leGFtcGxlLmNvbS8/cT1oZWxsbyB3b3JsZA== - Base64url:
aHR0cHM6Ly9leGFtcGxlLmNvbS8_cT1oZWxsbyB3b3JsZA
Note the / in the standard form becomes_ in base64url, and the trailing ==is dropped.
Why URLs need their own variant
The standard Base64 alphabet includes + and/. Both are reserved in URL syntax:
/is the path separator.+is, by convention in form-encoded query strings, interpreted as a space.=is the key-value delimiter in query strings.
Embedding a standard-Base64 string in a URL requires percent-encoding all three: + becomes%2B, / becomes %2F,= becomes %3D. The string gets longer and harder to read. base64url avoids the percent encoding by picking characters that are already URL-safe.
Why padding is optional in base64url
Standard Base64 always pads the output to a multiple of 4 characters using =. This isn’t mathematically necessary — the decoder can infer the original length from the output length modulo 4. Padding exists for streaming decoders that need to know the boundary between concatenated chunks.
Base64url skips padding because: (a) it’s redundant, (b) = needs percent-encoding in URLs, and (c) trailing equals signs in URLs sometimes confuse path parsers, CDNs, and cache keys.
Where each is used
| Use case | Variant |
|---|---|
| Email (MIME) attachments | Standard Base64 |
HTTP Basic Auth (Authorization: Basic …) | Standard Base64 |
Data URIs (data:image/png;base64,…) | Standard Base64 |
| JWT (all three segments) | Base64url, no padding |
| OAuth 2.0 PKCE code verifier/challenge | Base64url, no padding |
| WebPush message keys, JOSE/JWS/JWE | Base64url, no padding |
| Signed cookies (Rails, Django, etc.) | Base64url, no padding |
The decoding gotcha
Many libraries’ default Base64 decoder fails on base64url input. Python’s base64.b64decoderejects - and _; Node.js’s Buffer.from(s, "base64")is permissive but the inverse (.toString("base64")) outputs standard Base64.
Cross-language hacks: replace before decoding.
// base64url → standard Base64
const standard = base64url
.replace(/-/g, "+")
.replace(/_/g, "/")
.padEnd(Math.ceil(base64url.length / 4) * 4, "=");Better: use a library that explicitly supports base64url (Node 16+ has Buffer.from(s, "base64url"); Python has base64.urlsafe_b64decode).
The pragmatic rule
If the encoded value will appear in a URL, in a JWT, in a signed cookie, or as a path segment — use base64url. If it’s for an email attachment, HTTP header (other than cookies), or a JSON data URI — use standard Base64.
Our Base64 encoder handles both variants, with a toggle for the alphabet. The JWT decoder assumes base64url because the standard requires it.
Numeric facts
- Encoded length: both variants expand binary by exactly 4/3 (
ceil(n/3)*4). 100 input bytes → 136 output chars in standard Base64, or 134 chars unpadded in base64url. - Padding overhead: standard Base64 adds 0, 1, or 2
=chars per encoded string — at most a 1.5% size penalty for short payloads, negligible for long ones. - Percent-encoding cost in URLs: each
+ / =in standard Base64 expands to 3 bytes (%2B %2F %3D) when URL-encoded. For a 512-char JWT, that’s typically 30-60 wasted bytes — base64url avoids it entirely. - Alphabet size: 64 characters in both, so each output character carries exactly 6 bits of input.
- JWT segment sizes (RFC 7519): typical header 40-50 chars, payload 100-400 chars, HS256 signature 43 chars (exactly 32 bytes / 6 = 43 base64url chars, unpadded).
- Throughput: Node 20
Buffer.from(s, "base64")decodes at ~2 GB/s; Pythonbase64.b64decodeat ~600 MB/s on a 2024 laptop.
Decision matrix
| Where the value lives | Pick |
|---|---|
| URL path or query string | base64url, no padding |
| JWT (header / payload / signature) | base64url, no padding (RFC 7519) |
| OAuth 2.0 PKCE verifier | base64url, no padding (RFC 7636 §4.1) |
| Signed cookie (Rails / Django / Express) | base64url, no padding |
data: URI in HTML/CSS | standard Base64 with padding |
| MIME email attachment | standard Base64 with padding, 76-char line wrap (RFC 2045) |
| HTTP Basic Auth header | standard Base64 (RFC 7617) |
| WebPush subscription keys | base64url, no padding (RFC 8291) |
Sources
- RFC 4648 — The Base16, Base32, and Base64 Data Encodings — rfc-editor.org/rfc/rfc4648 (§4 standard, §5 URL-safe).
- RFC 7519 — JSON Web Token (JWT), mandates base64url with no padding — rfc-editor.org/rfc/rfc7519.
- RFC 7636 — OAuth 2.0 PKCE, §4.1 specifies base64url-encoded SHA-256 challenge — rfc-editor.org/rfc/rfc7636.
Frequently asked questions
- Can I decode base64url with a standard Base64 decoder?
- Sometimes — Node's Buffer.from is permissive, Python's b64decode is not. The cross-language safe approach: replace '-' with '+', '_' with '/', and pad to a multiple of 4 with '=' before decoding. Modern runtimes provide explicit base64url decoders (Node 16+, Python's urlsafe_b64decode) that handle this directly.
- Why does base64url drop padding?
- Three reasons: it's mathematically redundant (decoders can infer the original length from the output length mod 4), the '=' character needs percent-encoding in URLs, and trailing '=' confuses some path parsers, CDNs, and cache keys.
- Are JWT segments always base64url?
- Yes. RFC 7519 mandates base64url with no padding for all three JWT segments (header, payload, signature). Encoding a JWT with standard Base64 produces a token that won't verify on any compliant library.
- Does base64url save bytes compared to standard Base64?
- Marginally — same alphabet size means the same encoded length before padding. Dropping padding saves 0-2 bytes per encoding. The real saving is avoiding percent-encoding (each %XX is 3 bytes for 1), which can save 6-9 bytes per Base64 string in a URL.
Related
Published May 16, 2026