Skip to content

Comparison

Base64 vs base64url: two characters that matter

Same alphabet, different last two characters, different padding. One-line difference, totally different use cases.

By Published

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

PropertyStandard Base64Base64url
62nd character+-
63rd character/_
Padding= to multiple of 4Typically omitted
URL-safe?No (+, /, = need percent-encoding)Yes
Defined inRFC 4648 §4RFC 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 caseVariant
Email (MIME) attachmentsStandard 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/challengeBase64url, no padding
WebPush message keys, JOSE/JWS/JWEBase64url, 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; Python base64.b64decode at ~600 MB/s on a 2024 laptop.

Decision matrix

Where the value livesPick
URL path or query stringbase64url, no padding
JWT (header / payload / signature)base64url, no padding (RFC 7519)
OAuth 2.0 PKCE verifierbase64url, no padding (RFC 7636 §4.1)
Signed cookie (Rails / Django / Express)base64url, no padding
data: URI in HTML/CSSstandard Base64 with padding
MIME email attachmentstandard Base64 with padding, 76-char line wrap (RFC 2045)
HTTP Basic Auth headerstandard Base64 (RFC 7617)
WebPush subscription keysbase64url, no padding (RFC 8291)

Sources

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