Both turn readable input into a string full of unfamiliar characters, both show up constantly in URLs, and both get called "encoding" — so it's an easy pair to conflate. But they solve two completely different problems, and mixing them up is exactly what causes the classic "this token breaks when I put it in a link" bug.
The short answer
Base64 converts arbitrary binary data into printable text, so it can survive being carried through systems built for text. URL encoding (percent-encoding) converts text that's already text into a form that's safe inside one specific syntactic context — a URL — by escaping the characters that would otherwise be misread as part of the URL's structure. One is about representing data; the other is about escaping syntax. For the full mechanics of the first one, see What Is Base64 Encoding?
What each one is actually for
- Base64 — you reach for it when you have bytes that aren't text at all (an image, a file, a cryptographic hash or signature) and need to embed them somewhere that only accepts text: a JSON field, an XML document, an HTTP header, a
data:URL. - URL encoding — you reach for it when you have ordinary text (a search query, a filename, a form value) that might contain characters with special meaning in a URL —
&,=,?,/, spaces — and you need it to survive as a single, unambiguous path segment or query parameter value.
How they work, side by side
Base64 groups input into 3-byte chunks and re-maps each chunk onto 4 characters from a fixed 64-symbol alphabet (A–Z, a–z, 0–9, +, /). Every byte of input is transformed, whether or not it was "safe" to begin with — the output size is always about 4/3 (≈33%) larger than the input, no matter what that input contains.
URL encoding leaves almost everything untouched. It only replaces the specific characters that are unsafe or reserved in a URL, each with a % followed by two hex digits representing that character's byte value — %20 for a space, %3F for ?, and so on. A string of plain letters and digits comes out completely unchanged; a string that's mostly reserved punctuation can nearly triple in size, since every escaped byte turns 1 character into 3.
A concrete example
Take the raw bytes 0x12 0x00 0x7e. Standard Base64 encodes them as EgB+. Drop that straight into a URL and you're fine — until the data happens to contain a / instead, like c3ViamVjdHM/ (the Base64 of the string "subjects?"). Paste that into a URL path unescaped and the / reads as a path separator, silently splitting your token into two segments.
You have two ways to fix it, and they produce different results:
| Approach | Input | Output | Length |
|---|---|---|---|
| Percent-encode the standard Base64 | c3ViamVjdHM/ | c3ViamVjdHM%2F | 14 |
| Use the Base64URL alphabet instead | c3ViamVjdHM/ | c3ViamVjdHM_ | 12 |
Both decode back to the same bytes, but the URL-safe alphabet stays shorter because it swaps the two troublesome characters (+ → -, / → _) instead of escaping them, and conventionally drops the = padding too, since it's reconstructible from the string's length. This is the variant JWTs use for exactly this reason — it's why a JWT can sit directly in a URL query parameter with no additional escaping at all.
Size overhead: a real difference
- Base64 overhead is fixed: always ≈33% larger, regardless of content, because every byte gets re-encoded.
- URL-encoding overhead is content-dependent: 0% for a string of plain unreserved characters, up to 3x in the worst case, since only about 66 of the 256 possible byte values are left unescaped — every other byte turns 1 character into a 3-character
%XXsequence.
That worst case is exactly why you never percent-encode raw binary data directly — running arbitrary bytes through encodeURIComponent instead of Base64 first produces a far larger string than necessary, for data that Base64's fixed 33% overhead handles more predictably.
When to use which
- Binary data going anywhere (a file, hash, image, or opaque token) → Base64 — the URL-safe variant if it might end up in a URL, standard Base64 otherwise.
- Human-authored text going into a URL (a search term, a filename, form input) → URL encoding, specifically
encodeURIComponentfor a single value. It keeps the text mostly readable instead of turning it into an opaque blob. - Both, when unavoidable — if you must embed already-Base64-encoded data (standard alphabet) inside a URL you don't control the format of, percent-encode the result. But switching to Base64URL at the source is almost always the cleaner fix.
Common mistakes worth avoiding
- Assuming standard Base64 is already URL-safe. It isn't — the
+and/characters are legal Base64 output and legal URL syntax, just with two different meanings. +silently becoming a space. Many query-string parsers follow the olderapplication/x-www-form-urlencodedconvention where+means an encoded space — so a standard Base64 string with a+in it can get corrupted by a server that decodes it that way, with no error raised anywhere.- Percent-encoding raw bytes instead of Base64-encoding them first. Works in principle, wildly inflates the result in practice — see the overhead comparison above.
- Double-encoding. Running
encodeURIComponenton a string that's already percent-encoded turns%20into%2520, a classic source of mangled query parameters. encodeURIvs.encodeURIComponent.encodeURIassumes you're encoding a whole URL and deliberately leaves&,=, and?untouched — using it on a single query parameter value instead ofencodeURIComponentwill leave those characters unescaped and break the query string the moment your value contains one.
Try it yourself
Encode binary-safe text with Base64 Encode (toggle URL-safe output to see the alphabet swap in action), or escape text for a URL with URL Encode, which offers both component and full-URI modes. Both run entirely in your browser — nothing you paste in is ever sent anywhere.