Base64 vs URL Encoding: What Developers Keep Getting Wrong
A teammate once spent an afternoon debugging an OAuth callback that was silently corrupting tokens. The flow looked correct — the JWT was generated, the redirect URL was built, the query parameters were appended.
But the IDP kept returning 400 Bad Request.
He was Base64-encoding the entire redirect URL before passing it as a query parameter.
// What he wrote
const encoded = btoa("https://idp.example.com?state=xyz&code=abc");
// "aHR0cHM6Ly9pZHAuZXhhbXBsZS5jb20/c3RhdGU9eHl6JmNvZGU9YWJj"
The URL encoder on the other end had no idea what to do with that. It wasn't a malformed URL — it was the wrong kind of encoding entirely.
Base64 and URL encoding get confused constantly because both transform strings into something that looks scrambled. But they serve completely different purposes, and using one where the other belongs is a reliable way to generate tickets that sit in your backlog for days.
What Each Encoding Solves
Base64 converts binary data into ASCII-safe text so it can travel through text-based systems without corruption. Images, PDFs, file attachments — anything that isn't text to begin with. Base64 is a data format transform.
URL encoding (percent-encoding) makes characters safe to include in a URL. A space in a query string, a & in a parameter value, a # in a path segment — characters that have structural meaning in URIs get escaped so they're treated as literal data. URL encoding preserves URL validity.
| Base64 | URL Encoding | |
|---|---|---|
| Purpose | Transport binary as text | Make characters URL-safe |
| Input | Bytes (binary or text) | Text (URL components) |
| Output | ASCII characters (A-Z, a-z, 0-9, +, /, =) | Percent-encoded sequences (%XX) |
| Reversible | Yes | Yes |
| Size change | Increases ~33% | Increases per encoded char |
| Common in | JWTs, APIs, email, file transfer | Query strings, OAuth, form submits |
Where the Confusion Starts
They Appear Together in JWTs
JWT tokens are the biggest source of this confusion. A JWT header and payload are Base64URL encoded, and the token itself is then passed as a URL query parameter or in an Authorization header:
https://api.example.com/authorize?token=eyJhbGciOi...eyJ1c2VySWQiO...
Developers see Base64 inside a URL and conflate the two encoding systems. But the Base64 transforms the JWT's JSON into transport-safe text; the URL encoding makes that token safe inside query parameters. They're operating at different layers of the stack.
Both Produce Weird-Looking Characters
Side by side, both look like gibberish:
aGVsbG8gd29ybGQ= (Base64)
hello%20world%3Fq%3D1 (URL encoded)
The visual similarity tricks developers into assuming they're interchangeable. They're not.
Real Bugs Caused by This Confusion
1. Base64 in Query Parameters Without URL Encoding
Base64 strings can contain +, /, and =. These characters have special meanings in URL query strings:
/api/files?data=abc+def==ghi/
The + gets interpreted as a space in application/x-www-form-urlencoded parsing. The / can break path routing. The = confuses key-value parameter splitting.
Fix: either URL-encode the Base64 string, or use Base64URL (which replaces + with -, / with _, and strips =).
const safe = btoa(data).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2. URL-Encoding Something That Should Be Base64-Encoded
If you're embedding binary data in JSON, URL encoding is wrong:
// Wrong: binary image data URL-encoded
{ "image": "%89PNG%0D%0A%1A%0A" }
// Correct: binary image data Base64-encoded
{ "image": "iVBORw0KGgoAAAANSUhEUg..." }
URL encoding replaces individual dangerous characters. It doesn't convert binary into a text-safe representation. Your binary bytes will break JSON parsing.
3. Double Encoding in Redirect Flows
This one is particularly fun to debug because the URL looks correct in the browser bar:
const redirect = `https://app.example.com/callback?data=${btoa(payload)}`;
// The redirect URL containing Base64 gets placed inside another URL...
window.location = `https://auth.example.com?return=${encodeURIComponent(redirect)}`;
Now you have URL encoding wrapping Base64. On the receiving end, someone does:
const inner = new URLSearchParams(window.location.search).get("return");
// This auto-decodes the URL encoding
atob(inner); // Now decode the Base64
If the decoding order is wrong (URL-decode after Base64-decode, or double-decode), you get garbled output or a crash.
Base64URL: The Middle Ground
Base64URL exists specifically to make Base64 safe in URLs without requiring a separate URL-encoding step:
Standard Base64: YWJjKysvLz09 (contains +, /, =)
Base64URL: YWJjKysvLz09 (+ → -, / → _, = removed)
JWTs use Base64URL for exactly this reason — the token might travel in a query parameter, a cookie, or an HTTP header, so the encoding needs to be URL-safe from the start.
When you're decoding JWT segments manually, you need Base64URL decoding, not standard Base64. Our JWT Decoder handles this automatically, which saves a step compared to manual atob() conversion.
Which Encoding to Use When
Use Base64 when:
- Embedding binary files (images, PDFs) in JSON API payloads
- Encoding JWT header and payload segments
- Sending email attachments (MIME)
- Creating data URIs for inline HTML assets
Use URL encoding when:
- Building query strings with user-supplied values
- Constructing OAuth redirect URLs
- Passing special characters (
&,=,#,?) as literal data in URLs - Submitting HTML forms (
application/x-www-form-urlencoded)
Use Base64URL when:
- You need Base64-encoded data inside a URL and want to skip an extra encoding step
- Working with JWTs
- Encoding tokens that flow through OAuth and OIDC redirects
A Quick API Debugging Scenario
You're integrating a file upload API that expects a Base64-encoded image in a JSON body. You send:
{
"filename": "screenshot.png",
"data": "iVBORw0KGgoAAAANSUhEUg..."
}
And get back 400 Bad Request. The error message says nothing useful.
Checklist:
- Is the Base64 valid? Paste it into the Base64 Encoder & Decoder and try decoding. If it fails, the payload is corrupted.
- Are you sending raw bytes instead of Base64? Read the raw request body. If you see non-printable characters, the binary data wasn't encoded.
- Are
+characters being mangled? Some middleware converts+to spaces. If your Base64 contains+(standard encoding), consider Base64URL instead or URL-encode the entire JSON body.
Related debugging workflow: How to Fix Invalid Base64 String Errors.
FAQ
Is Base64 the same as URL encoding?
No. Base64 converts binary data into ASCII-safe text. URL encoding escapes characters that would break URL syntax. They solve different problems and produce different output.
Why do JWTs use Base64URL instead of standard Base64?
Because JWTs travel through URLs, HTTP headers, and cookies. Standard Base64 characters (+, /, =) can break URL parsing and HTTP transport. Base64URL replaces those characters to be URL-safe by default.
Should I Base64-encode URLs?
Almost never. URLs should use percent-encoding for their unsafe characters. Base64-encoding an entire URL changes the data itself rather than making it safe for transport.
What happens if I use standard Base64 in a URL?
+ may be interpreted as a space in query strings. / can break path routing. = can be confused with key-value separator syntax. Your URL might work in some contexts and fail in others, depending on how the server parses it.
Can I use URL encoding for binary data?
No. URL encoding replaces individual unsafe characters with %XX sequences. It doesn't convert arbitrary binary bytes into a text-safe format. Binary data will still contain non-printable characters that break text protocols.
If you work with APIs, JWTs, or file uploads regularly, having both a Base64 Encoder & Decoder and a JWT Decoder bookmarked makes debugging encoding-related issues much faster. The Base64 tool handles standard, URL-safe, and mixed formats, which covers most of the edge cases you'll hit in real API development.