devkult_
tools24converters26
home/blog/base64-vs-base64url
encodingbase64jwt

Base64 vs Base64URL: What's the Difference?

If you've ever decoded a JWT by hand and found that the standard Base64 decoder choked on it, you've already met the difference between Base64 and Base64URL. They encode the exact same bytes — they just disagree on two characters and on padding.

The same data, a different alphabet

Base64 represents binary data using 64 printable characters: A–Z, a–z, 0–9, and two symbols — + and /. Every 3 bytes of input become 4 output characters, and the result is padded with = so its length is always a multiple of four.

That + and / are the problem. Both are reserved characters in URLs (/ is a path separator, + often decodes to a space), and = has meaning in query strings. So if you drop a standard Base64 string into a URL, it can get mangled the moment something tries to parse it.

Base64URL fixes this with two substitutions and one omission:

  • + becomes - (minus)
  • / becomes _ (underscore)
  • trailing = padding is usually dropped

That's the entire difference. The 62 alphanumeric characters are identical; only the two symbols and the padding change.

bytes:        0xFB 0xFF 0xBF
Base64:       +/+/
Base64URL:    -_-_

Why JWTs use Base64URL

A JSON Web Token is three Base64URL-encoded segments joined with dots:

header.payload.signature

Tokens travel in URLs, Authorization headers, and cookies — all places where +, /, and = cause trouble. So the JWT spec (RFC 7519) mandates Base64URL without padding for every segment. That's why a generic Base64 decoder sometimes fails on a JWT: it sees a - or _ that isn't in its alphabet, or it expects padding that was stripped.

If you want to inspect a token without writing code, paste it into the JWT decoder — it normalizes Base64URL back to standard Base64, re-adds the padding, and shows you the decoded header and payload.

Converting between the two

Going from Base64URL to standard Base64 is a find-and-replace plus restoring the padding:

function base64UrlToBase64(input) {
  let s = input.replace(/-/g, "+").replace(/_/g, "/");
  while (s.length % 4) s += "=";   // restore padding
  return s;
}

And the reverse:

function base64ToBase64Url(input) {
  return input.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

For a one-off, the Base64 encoder/decoder handles standard Base64 directly in your browser — nothing is sent to a server.

A common gotcha: Base64 is not encryption

Worth repeating because it bites people: Base64 (and Base64URL) is an encoding, not encryption. Anyone can decode it instantly. A JWT payload is readable by anyone who intercepts the token — the signature proves it wasn't tampered with, but it does nothing to hide the contents. Never put secrets in a Base64 string and assume they're protected.

Quick reference

Base64 Base64URL
+ yes - instead
/ yes _ instead
= padding required usually dropped
URL-safe no yes
Used by email, data URIs JWT, URLs, cookies

If you remember one thing: same bytes, two characters swapped, padding optional. Everything else is identical.

More from the blog