Base64 vs Base64URL: what is the difference?

Dark navy cover card with the title 'Base64 vs Base64URL: what is the difference?' in white and a green accent bar

Base64 vs Base64URL comes down to two characters and the padding. Standard Base64 uses + and / for its last two values and pads with =. Base64URL uses - and _ instead, and most specs that use it drop the = padding. Both encode the same bytes, but a strict decoder for one will often reject the other.

The character difference between Base64 and Base64URL

The only alphabet difference between Base64 and Base64URL is values 62 and 63. Everything else, A-Z, a-z and 0-9, is identical.

RFC 4648, the standard that defines both, says the URL-safe encoding "is technically identical to the previous one, except for the 62:nd and 63:rd alphabet character."

Value Base64 (RFC 4648 Section 4) Base64URL (RFC 4648 Section 5)
Below 62 A-Z, a-z, 0-9 same
62 + - (minus)
63 / _ (underline)
Padding = required by default often skipped

The RFC also warns against mixing the names. Base64URL "should not be regarded as the same as the "base64" encoding and should not be referred to as only "base64"."

Why standard Base64 breaks in URLs

Standard Base64 breaks in URLs because +, / and = already mean something there. A slash splits a path, and = separates keys from values in a query string.

RFC 4648 Section 3.4 names the problem directly: "the non-alphanumeric characters (in particular, "/") may be problematic in file names and URLs." For padding, Section 5 adds that "=" is "typically percent-encoded when used in an URI."

Say you put a Base64 token in a path as /reset/ab+c/d==. The router sees two path segments instead of one, the token no longer decodes, and the user sees an "invalid link" page. Switching to Base64URL removes all three problem characters at once.

How padding differs

Padding is where the two variants differ most in practice. Base64 pads with = so the output length is a multiple of four. Base64URL may skip that padding, and the JWT family of specs always does.

The base rule in RFC 4648 Section 3.2 is strict: implementations "MUST include appropriate pad characters at the end of encoded data unless the specification referring to this document explicitly states otherwise." Section 5 then allows skipping it "if the data length is known implicitly."

RFC 7515, the JSON Web Signature spec, uses that permission. Its Base64url Encoding has "all trailing '=' characters omitted" and no "line breaks, whitespace" either. That is why a JWT header such as eyJhbGciOiJub25lIn0 has no = at the end. See our post on the JWT alg none attack for where that header comes from.

How common libraries handle Base64URL

Libraries do not agree on padding defaults, and this is the source of most "invalid character" and "incorrect padding" errors.

Library or spec URL-safe alphabet Padding by default
Browser btoa() No option Pads with =
Browser Uint8Array.toBase64() alphabet: "base64url" Pads unless omitPadding: true
Python base64.urlsafe_b64encode Yes Pads unless padded=False (Python 3.15+)
Go base64.URLEncoding Yes Pads
Go base64.RawURLEncoding Yes No padding
JWS / JWT (RFC 7515) Yes Never pads

Browser JavaScript. MDN's toBase64() page lists an alphabet option and an omitPadding flag whose default is false. It reached Baseline in 2025, so older browsers may lack it. The older btoa() has no URL-safe option and throws InvalidCharacterError on characters above one byte.

const bytes = new Uint8Array([29, 233, 101, 161]);
bytes.toBase64();                       // "HelloQ=="
bytes.toBase64({ omitPadding: true });  // "HelloQ"
bytes.toBase64({ alphabet: "base64url", omitPadding: true });

Python. The Python base64 docs say urlsafe_b64encode "substitutes - instead of + and _ instead of /". Its output "can still contain = if padded is true (default)." Version 3.15 added the padded parameter and made urlsafe_b64decode stop requiring padding by default. On older versions you strip and restore = yourself. Our guide to decoding Base64 in Python covers the older pattern.

Go. The encoding/base64 package names it best. URLEncoding is "typically used in URLs and file names", and RawURLEncoding is the same "but omits padding characters."

How to convert Base64URL back to Base64

To convert Base64URL to Base64, swap the two characters back and restore the padding. RFC 7515 Appendix C spells out the padding rule using the string length mod 4.

  1. Replace every - with + and every _ with /.
  2. Check the length mod 4. If it is 0, add nothing.
  3. If it is 2, add ==. If it is 3, add =.
  4. If it is 1, stop: the input is malformed and no padding fixes it.

In the RFC's own example, the bytes 3 236 255 224 193 encode to A-z_4ME in Base64URL. That string has 7 characters, so mod 4 is 3, and the standard form is A+z/4ME=.

function base64urlToBase64(s) {
  s = s.replace(/-/g, '+').replace(/_/g, '/');
  if (s.length % 4 === 1) throw new Error('malformed base64url');
  return s + '='.repeat((4 - (s.length % 4)) % 4);
}

To go the other way, the same appendix strips trailing = and swaps + for - and / for _. If you need a quick check from a terminal, see how to decode Base64 on the command line.

Which one should you use?

Use Base64URL whenever the value goes into a URL, a filename, a cookie or a token. Use standard Base64 for email attachments, data URIs and anywhere a spec names plain Base64.

My take: for anything new, default to Base64URL without padding and write the padding-restore step into the decoder once. The padding carries no data, since the length already tells you how many bytes are left. Keeping it buys you a character you then have to percent-encode.

The real limitation is interoperability. If the other side uses a strict standard decoder, such as atob() in an older codebase, unpadded Base64URL will fail until you convert it. Agree on the variant with whoever reads your data, and say "base64url" in the docs rather than just "base64".

You can try both forms in our Base64 encoder, which has a URL-safe Base64URL output option. If you are reading tokens rather than encoding data, decoding a JWT without the secret shows the same alphabet in use.

FAQ

Is Base64URL the same as Base64?

No. RFC 4648 says Base64URL should not be regarded as the same as base64. It uses - and _ in place of + and /, and specs such as JWS also drop the = padding, so a strict Base64 decoder can reject it.

Does Base64URL use padding?

It depends on the spec using it. RFC 4648 lets a spec skip padding when the length is known, and RFC 7515 (used by JWT) omits all trailing = characters. Many libraries still pad by default.

How do I convert Base64URL to normal Base64?

Replace - with + and _ with /, then add = until the length is a multiple of four. RFC 7515 Appendix C gives the rule: length mod 4 of 2 needs two =, 3 needs one, and 1 means the input is malformed.

Why does JWT use Base64URL instead of Base64?

JWTs travel in URLs, headers and cookies. The + and / characters and the = pad cause trouble there, so the JWS spec uses the URL-safe alphabet with no padding and no line breaks.

Sources