Base64 vs. URL encoding: when to use which
Base64 and URL encoding (also known as percent-encoding) are two of the most commonly used text transforms in web development. They look superficially similar โ both are ways of representing arbitrary data using a restricted set of characters โ but they solve different problems and are not interchangeable. Mixing them up causes a surprising number of production bugs: mangled query parameters, broken download URLs, authentication failures where a token silently decodes to garbage. This guide walks through when each one is the right choice, the common ways developers get them wrong, and the edge cases that bite you in production.
What each one is actually for
Base64: binary โ ASCII
Base64 takes arbitrary bytes (including non-printable ones) and represents them using 64 printable ASCII characters: AโZ, aโz, 0โ9, plus + and /, with = used for padding. Every three input bytes become four output characters. That means the output is about 33% larger than the input, but โ crucially โ it's pure ASCII text that can be dropped into any medium that accepts text: JSON values, email bodies, HTTP headers, XML, log files.
The canonical use cases are:
- Embedding binary data (images, certificates, small files) inside text formats.
- Encoding credentials for HTTP Basic auth โ the
Authorization: Basicheader carriesbase64(username:password). - Data URIs in CSS and HTML:
data:image/png;base64,.... - MIME email attachments.
- The parts of a JWT (which uses Base64URL, a small variant โ more on that below).
URL encoding: reserved characters โ %HH
URL encoding (RFC 3986 percent-encoding) replaces characters that have structural meaning in a URL, or that aren't safe in a URL, with % followed by the two-digit hexadecimal byte value. A space becomes %20, a / becomes %2F, a ? becomes %3F, and so on. Any byte outside the unreserved set (AโZ, aโz, 0โ9, and -_.~) either must be or may be percent-encoded depending on context.
You use URL encoding when inserting a value into one of the structural positions in a URL: a path segment, a query string parameter, or a fragment. The goal is to prevent characters inside your value from being interpreted as URL structure. If a query parameter's value contains an &, and you don't encode it, the server will interpret the rest of the value as the next parameter.
The mental model: different alphabets, different goals
Base64 is a total transform: it re-encodes every byte in the input, producing output that happens to be URL-unsafe-but-ASCII. URL encoding is a partial transform: it only touches characters that are unsafe in a URL context, and leaves normal letters and digits alone.
The best way to remember the distinction:
- Base64: "I have arbitrary bytes, I want to transport them through a text-only channel."
- URL encoding: "I have text, I want to safely place it inside a URL structure."
Base64URL: the variant that matters
Standard Base64 uses + and / as two of its 64 characters. Both have structural meaning in URLs (/ is a path separator; + is a space in application/x-www-form-urlencoded). If you put a standard Base64 string into a URL, something will eventually interpret it wrong.
RFC 4648 defines Base64URL to fix this: use - instead of +, and _ instead of /. Padding = characters are usually stripped as well, since they also need to be percent-encoded in URLs (= is the query-param separator). Base64URL is what JWTs use. When you paste a JWT into a decoder, the - and _ you see are part of this variant.
If you take a Base64URL string and run it through standard Base64 decoding, many libraries will throw an error on the - and _. Conversely, a standard Base64 string with + or / can silently corrupt when passed through a URL.
Common bugs from mixing them up
Double-encoding query parameters
Suppose you want to pass the value hello world as a query parameter. You run it through encodeURIComponent and get hello%20world. You then โ because you've seen Base64 used for tokens โ decide to "be safe" and Base64-encode the encoded form. Now the other end has to know to Base64-decode and URL-decode, in the right order. The more common error is the reverse: the other end URL-decodes once, gets a Base64 string, decodes that, gets bytes, and gets confused when those bytes aren't what was sent originally. Don't stack encodings without a reason.
Forgetting to URL-encode Base64 in a URL
If you generate a standard Base64 string like abc+def/ghi= and put it in a URL, three things will bite you:
+in a query value gets interpreted as space on the server (application/x-www-form-urlencodedrule)./in a path segment gets interpreted as a path separator.=is typically fine but some strict parsers treat it as reserved.
The fix is either (a) use Base64URL instead, or (b) percent-encode the whole Base64 string before putting it in the URL. Both work; Base64URL is tidier.
Encoding the whole URL instead of just a component
A classic mistake:
// wrong โ mangles : and / in the URL itself
encodeURIComponent('https://example.com/api?q=hello world')
// โ "https%3A%2F%2Fexample.com%2Fapi%3Fq%3Dhello%20world"
That's the right output only if the URL is itself a value being passed as a redirect target or OAuth parameter. If you just want a working URL, only encode the value of each query parameter:
const q = encodeURIComponent('hello world');
const url = `https://example.com/api?q=${q}`;
// โ "https://example.com/api?q=hello%20world"
encodeURI vs. encodeURIComponent
JavaScript ships two URL-encoding functions, and the choice between them trips up most developers at least once:
encodeURIleaves reserved characters alone::,/,?,&,=,#. It's intended for encoding a full URL that's already structurally correct. You almost never want this.encodeURIComponentencodes everything except unreserved characters. It's intended for encoding a component โ a single query value, a single path segment. This is what you want in 95% of cases.
Rule of thumb: unless you are literally taking an already-assembled URL and preparing it for a context where only the non-reserved characters need escaping, use encodeURIComponent.
When to reach for which: a cheat sheet
| Situation | Use |
|---|---|
| Embedding binary data (image, cert, file) in JSON or HTML | Base64 |
| HTTP Basic auth header | Base64 |
Data URI (data:image/png;base64,...) | Base64 |
| Inside a URL path or query value | URL encoding (via encodeURIComponent) |
| JWT parts, OAuth state parameters, share links | Base64URL (no padding) |
Form POST body (application/x-www-form-urlencoded) | URL encoding per-value |
| Email subject line with special characters | RFC 2047 encoding (a Base64 variant) |
| MIME email attachments | Base64 |
A brief word on what neither of these is
Neither Base64 nor URL encoding is encryption. Both are reversible with zero secret input โ any observer who captures the encoded value can recover the original in one step. If you're trying to hide a value from someone who has the encoded form, you need actual cryptography: symmetric encryption with a key they don't have, or signed tokens whose payload is public but whose authenticity you can verify.
This is worth emphasizing because "obfuscation by Base64" keeps showing up in real codebases: API keys in a config file, JWT payloads treated as if they were secret, Base64-wrapped IDs used as access tokens. Every one of those is an incident waiting to happen.
Test your understanding
If you want to internalize the difference, grab any string and try it through both on DevDecoder. Notice that Base64 encoding always transforms every character (even plain ASCII), while URL encoding leaves normal letters and digits untouched. That asymmetry is the whole story.