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.

Common questions

Frequently asked.

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.

新しい記事を毎週お届け。

実践的な開発者向けガイド。スパムなし。いつでも購読解除可能。

Tools mentioned

Pick up where the post leaves off.

Keep reading

More from the field notes.