Skip to content
All posts

Base64URL vs Base64: when the extra characters matter

The + / = characters in standard Base64 break URLs, filenames, and JWT headers. Base64URL is the fix — and it's almost but not quite a drop-in replacement.

DDDev DeskDeveloper Tools EditorPublished April 26, 20263 min readbeginner

# The characters that cause trouble

Standard Base64 uses 64 characters: A–Z, a–z, 0–9, plus + and /, with = for padding.

Three of those cause problems in common places:

  • + in a URL query string means space. So ?token=abc+def parses as token=abc def, destroying your payload.
  • / is a path separator. A filename or URL component containing / breaks trivially.
  • = is the query-string separator. Padding at the end of a Base64 token collides with URL syntax.

# The fix: Base64URL

Defined in RFC 4648 § 5. Two tiny changes:

  • +-
  • /_
  • Padding (=) is optional, often dropped entirely

Base64:    SGVsbG8gV29ybGQ=
Base64URL: SGVsbG8gV29ybGQ

Same bytes decoded. Safe to drop into a URL without %-encoding, safe as a filename, safe as a JWT segment.

# Where you'll see it

  • JWT — all three parts (header, payload, signature) are Base64URL.
  • OAuthcode_challenge, state, and PKCE values.
  • WebAuthn — credential IDs and public keys.
  • Short-link generators — encoding numeric IDs as URL-safe text.
  • Data URLs with a URL-encoded mode — less common, but permitted.

# Converting between them

<div class="callout callout-tip" role="note"><div class="callout-title">Tip</div><div class="callout-body"><p>If a library gives you Base64 but the destination wants Base64URL, you can convert in-place: replace <code>+</code> with <code>-</code>, <code>/</code> with <code>_</code>, and strip trailing <code>=</code>. Going back adds them in the same order.</p></div></div>


function toBase64Url(b64) {
  return b64.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
}
function fromBase64Url(b64url) {
  const padded = b64url + "=".repeat((4 - b64url.length % 4) % 4);
  return padded.replaceAll("-", "+").replaceAll("_", "/");
}

# Common bugs

  • Using a library's Base64 encoder and dropping into a URL without converting first. The + gets URL-encoded to %2B by the client but the server decodes it back to space, not +. Silent data corruption.
  • Forgetting padding on decode. Many Base64URL producers omit =; if your decoder is strict, it rejects the input.
  • Hand-rolling the conversion with a single replace (which only replaces the first match) instead of replaceAll or a regex.

# In the browser

Our Base64 tool offers both modes via a toggle. Paste any Base64URL string, flip the switch, see the decoded bytes.

Frequently asked questions

Can I decode a Base64URL string with a regular Base64 decoder?

Not directly — swap `-` back to `+` and `_` back to `/`, then pad with `=` to a multiple of 4 characters. Many decoders have a `urlSafe` option that does this automatically.

Why does Base64URL drop padding?

The `=` character has special meaning in URL query strings (`key=value`). Dropping it keeps the Base64 payload usable in a URL parameter without escaping. Decoders know how much padding to infer from the length.

New posts, once a week.

Practical developer guides. No spam. Unsubscribe any time.

Tools mentioned

Keep reading