# 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](/tools/security/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:

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

And the reverse:

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

For a one-off, the [Base64 encoder/decoder](/tools/encoding/base64-encode) 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.
