Base64, URL encoding, and percent-encoding: what each one is actually for
Three encodings developers mix up constantly. None of them is encryption — here's what each one solves and when to reach for it.
"Encoding" is one of the most overloaded words in software, and a lot of confusion — and a few real bugs — come from conflating three different things that all get called encoding: Base64, URL encoding (percent-encoding), and character encoding like UTF-8. They solve different problems, and using the wrong one produces subtly broken data that often works in testing and fails in production.
Let's separate them cleanly, then look at when each belongs.
First, the thing they all have in common
None of these is encryption. Encoding is reversible by anyone, with no key. Its job is to represent data in a form some channel can safely carry — not to hide it. If your goal is secrecy, encoding does nothing for you; you want encryption. If your goal is "get this data through a system that only accepts certain characters," encoding is exactly the tool. Keep that distinction and half the confusion evaporates.
Base64: making binary safe for text-only channels
Base64 solves one specific problem: you have arbitrary binary data, and you need
to move it through something that only handles text. Email bodies, JSON strings,
JWT payloads, data: URIs, and HTTP headers were all designed for text, not raw
bytes. Base64 takes any bytes and re-expresses them using only 64 "safe"
characters: A–Z, a–z, 0–9, +, and /, with = for padding.
The mechanism is elegant. It takes three bytes (24 bits) at a time and regroups
them into four 6-bit chunks, each of which indexes into that 64-character
alphabet. Three bytes in, four characters out — which is why Base64 inflates
your data by roughly 33%. That overhead is the price of text-safety, and it's
why you Base64-encode a small avatar into a data: URI but stream a large video
as raw bytes.
Encode the word Man and you get TWFu. Encode Hi (two bytes, not a multiple
of three) and you get SGk= — the = is padding that signals the last group was
short.
Where you'll meet Base64:
- Embedding images or fonts directly in HTML/CSS as
data:URIs - The header and payload of every JWT
- HTTP Basic Auth (
Authorization: Basic base64(user:password)) - Encoding binary attachments in email (MIME)
- Storing binary blobs in JSON or a text column
Base64URL: the web-safe variant. Standard Base64 uses + and /, which both
have special meaning inside URLs (and = does too). So there's a variant called
Base64URL that swaps + → -, / → _, and usually drops the padding. This is
what JWTs and many web APIs use. It's the same idea, with a two-character
substitution so the result can live safely in a URL or filename.
URL encoding: making text safe for URLs
URL encoding — properly called percent-encoding — solves a different problem:
a URL has structural characters, and you need to include data that might contain
those same characters. A ? starts the query string, & separates parameters,
/ separates path segments, # starts a fragment. So what happens when a search
query is literally p&l report (2026)? If you drop it into a URL raw, the &
looks like a parameter separator and everything after it breaks.
Percent-encoding replaces any unsafe character with % followed by its
hexadecimal byte value. A space becomes %20, & becomes %26, ? becomes
%3F, and so on. Our query becomes:
p%26l%20report%20%282026%29
Now the whole thing is unambiguously one value, and the server decodes it back to the original on the other side.
A crucial subtlety: which characters to encode depends on where in the URL you
are. A / is fine in a path but must be encoded in a query parameter value.
This is why languages give you two functions. In JavaScript,
encodeURI() encodes a whole URL and leaves structural characters like / and
? alone, while encodeURIComponent() encodes a single piece (one query value)
and escapes everything. The rule of thumb: use encodeURIComponent() on each
individual value you drop into a URL, and encodeURI() only when you're handling a
complete URL. Mixing them up is a very common source of double-encoding bugs,
where a %20 becomes %2520 because it got encoded twice.
UTF-8: the character encoding underneath
The third thing called "encoding" is the most fundamental: how text characters map
to bytes in the first place. When you type café, the é isn't a byte — it's a
Unicode code point (U+00E9), and UTF-8 is the near-universal scheme for turning
code points into bytes. In UTF-8, é becomes two bytes (0xC3 0xA9); an emoji
might take four.
This matters because Base64 and URL encoding both operate on bytes, not
characters — so the character encoding happens first. Encode café as a URL
parameter and the é becomes %C3%A9 (two percent-escapes, because it's two
UTF-8 bytes), not a single %E9. Mismatched character encodings are why you
sometimes see é where an é should be: some layer decoded UTF-8 bytes as if
they were Latin-1.
Putting it in order
For a value traveling from your app into a URL, the layers stack like this:
- Characters → bytes via UTF-8 (usually automatic).
- Bytes → URL-safe text via percent-encoding, if it's going in a URL.
- Bytes → text via Base64, if it's binary going into a text-only field.
You rarely apply Base64 and URL encoding to the same value — except that Base64
output can itself contain + and /, which is exactly why Base64URL exists to
skip a second encoding pass.
Debugging encoding problems
When text comes out garbled, the fastest diagnosis is to run the value through the
right decoder and see what you get back. A Base64 Encoder/Decoder
and a URL Encoder/Decoder let you paste a mangled value
and reverse each layer one at a time — if Base64-decoding produces readable text,
you know it was Base64; if percent-decoding fixes it, it was URL-encoded; and if
you see %2520, you've found a double-encoding bug. Working the layers backward is
almost always faster than staring at the broken string.
The takeaway
Three encodings, three jobs. UTF-8 turns characters into bytes. Base64 turns bytes into text-safe characters (at a 33% size cost) so binary can ride through text-only channels. URL/percent-encoding escapes characters that would otherwise break a URL's structure. None of them is encryption — they're all freely reversible — and the most common bug is applying the wrong one, or the right one twice. Know what each solves and encoding stops being a mystery.