base64url is a small variant of Base64 that replaces the two characters unsafe in URLs, plus and slash, with hyphen and underscore, and usually drops the equals-sign padding, so the encoded text can travel in a URL, a filename, or an HTTP header without any further escaping. It encodes exactly the same bytes as standard Base64 using the same algorithm; only the last two symbols of the alphabet and the padding differ. That tiny change is why JSON Web Tokens, many APIs, and lots of identifiers use base64url. This article explains the difference precisely, why it exists, and how to convert between the two variants without corrupting data.
What does Base64 do in the first place?
Base64 encodes arbitrary binary data as text by mapping every three bytes to four printable ASCII characters drawn from a 64-symbol alphabet. Three bytes are 24 bits, and 24 bits split evenly into four groups of six, each group indexing one of 64 characters. The point is transport: many channels, such as email bodies, URLs, and text-based data formats, cannot safely carry raw bytes, so Base64 turns those bytes into a safe subset of ASCII that survives the trip. The standard alphabet (defined in RFC 4648) is A-Z, then a-z, then 0-9, and finally two extra symbols. Those two extra symbols are where the variants diverge.
How exactly does base64url differ from standard Base64?
base64url differs in just two places: it uses - and _ for the 62nd and 63rd characters instead of + and /, and it typically omits the = padding. The first 62 characters of the alphabet are identical in both variants; only the final two change. The table makes the whole difference explicit:
| Value | Standard Base64 | base64url |
|---|---|---|
| 0-25 | A-Z |
A-Z (same) |
| 26-51 | a-z |
a-z (same) |
| 52-61 | 0-9 |
0-9 (same) |
| 62 | + |
- |
| 63 | / |
_ |
| Padding | = |
usually omitted |
Because the two alphabets share their first 62 characters, most short strings look identical in both variants. The difference only becomes visible when the encoded bytes happen to produce a 62nd or 63rd symbol, which is exactly when a URL would otherwise break.
Why are + and / a problem in URLs?
The characters + and / already have jobs in a URL, so a Base64 string containing them can be misread or silently altered by the systems it passes through. A slash is the path separator, so a / in the middle of a token looks like a new path segment. A plus sign, in the query-string form encoding used by web forms, means a space, so a + in a token can be decoded back into a space by some layer and corrupt the value. Standard Base64 also uses = for padding, and = is reserved in query strings as the separator between a key and a value. Any of these can require additional percent-encoding, and worse, can be changed by a well-meaning intermediary. base64url sidesteps all three problems: - and _ are unreserved in URLs, and by dropping = the padding issue disappears too. The result is a string you can drop into a URL, a filename, or a header untouched.
Why do JSON Web Tokens use base64url?
A JWT uses base64url because the token is designed to live in exactly the places where +, /, and = cause trouble: URLs, the HTTP Authorization header, and cookies. A JWT is three base64url-encoded parts, the header, the payload, and the signature, joined by dots, as in header.payload.signature. If those parts used standard Base64, a token containing a slash could be mangled when placed in a URL, and a token with padding could collide with query-string syntax. By using base64url with no padding, the whole token is a run of URL-safe characters plus dots, so it can be sent as a bearer token in a header or embedded in a link without any escaping. The dot separator is itself a safe character, which is why it was chosen to join the parts. Decoding each part is a routine base64url decode, and doing it in a browser-based Base64 encoder and decoder lets you inspect a token’s header and payload without pasting it into a remote server, which matters because a real JWT often carries identifying claims you should not leak.
What happened to the padding?
The = padding is optional in base64url and is usually stripped, because a decoder can reconstruct it from the length of the string. Padding exists in standard Base64 to make the output length a multiple of four when the input byte count is not a multiple of three. One leftover byte produces two Base64 characters plus ==; two leftover bytes produce three characters plus =. But the padding carries no information the length does not already imply, so base64url drops it to keep the string fully URL-safe. When you need to decode an unpadded base64url string with a decoder that expects padding, you add = characters until the length is a multiple of four: a string whose length mod 4 is 2 needs ==, and one whose length mod 4 is 3 needs a single =. A length that is a multiple of four already needs none, and a remainder of 1 is invalid and signals a corrupted string.
How do I convert between the two variants?
Converting is a pair of character substitutions plus padding adjustment, and it never touches the underlying bytes. To turn standard Base64 into base64url, replace + with -, replace / with _, and remove any trailing =. To go the other way, replace - with +, replace _ with /, and append = until the length is a multiple of four. In pseudocode:
// standard Base64 -> base64url
urlsafe = standard.replace('+', '-')
.replace('/', '_')
.replace(/=+$/, ''); // drop padding
// base64url -> standard Base64
standard = urlsafe.replace('-', '+')
.replace('_', '/');
while (standard.length % 4 != 0)
standard += '='; // restore padding
Because only those two characters and the padding differ, the conversion is lossless and reversible. The one thing to watch is doing the substitution in the right direction; swapping - to / instead of +, for instance, corrupts the decode. When a base64url value will not decode, the usual culprit is missing padding or a substitution done backwards, and re-adding the padding is the first fix to try.
Is base64url a security measure?
No. base64url, like every form of Base64, is an encoding, not encryption, and provides no confidentiality whatsoever. Anyone who receives the string can decode it back to the original bytes with a trivial, well-known algorithm. This is worth stating plainly because the unfamiliar-looking characters sometimes give the impression of obfuscation. A JWT payload encoded with base64url is fully readable to anyone who copies the middle section and decodes it, which is exactly why you must never place a secret in a token’s payload and why a JWT’s protection comes from its cryptographic signature, not from the encoding. Treat base64url purely as what it is: a way to carry bytes safely through URLs and headers, with no security properties of its own.
base64url is one of the smallest specifications you will meet, a two-character swap and a dropped pad, yet it quietly makes tokens and identifiers work everywhere a URL goes. Remember that it encodes the same bytes as standard Base64, that - and _ stand in for + and /, that padding is optional and reconstructible from length, and that none of it hides anything. With those facts, converting and decoding base64url is entirely routine.