URL Encoding (Percent-Encoding) Explained

September 26, 2026 · Web Development

Every %20 you've ever seen in a URL is percent-encoding (URL encoding): the web's mechanism for smuggling characters that URLs can't natively carry — spaces, non-English text, and symbols that already mean something in URL syntax. It's simple, but the edge cases (+ vs %20, double encoding, Base64 in query strings) cause real bugs. Here's the complete picture.

Why URLs can't contain just any character

URLs are built from a limited ASCII alphabet, and several characters are structural: ? starts the query string, & separates parameters, # starts the fragment, / separates path segments. So what happens when your data contains those characters — a search for fish & chips, a filename with a ?? Without escaping, the URL parser can't tell data from syntax. Percent-encoding is the escape hatch: represent the offending character as % followed by its two hex digits.

How percent-encoding works

The rule: % + two hexadecimal digits = one byte. hello world becomes hello%20world because space is byte 0x20. ? (0x3F) becomes %3F, & (0x26) becomes %26 — so fish & chips as a query value is fish%20%26%20chips, and the parser no longer mistakes the & for a parameter separator.

Case doesn't matter (%2f = %2F), but uppercase is conventional. The % itself is encoded as %25 — which is exactly how double-encoding bugs are born (see below).

Try it live: encode fish & chips? in our free URL Encoder/Decoder and watch each character become its %XX form.

Reserved vs unreserved characters

RFC 3986 divides characters into two camps:

  • Unreserved — never encode: A–Z a–z 0–9 - _ . ~. These are always safe as-is.
  • Reserved — encode when used as data: : / ? # [ ] @ ! $ & ' ( ) * + , ; =. As syntax they keep their meaning (https:// needs its : and /); as data inside a value they must be escaped so the parser doesn't misread them.

This data-vs-syntax distinction is the core insight: the same character is encoded or not depending on its role. That's why you encode values, not whole URLs.

Non-English characters: UTF-8 first, then encode

Percent-encoding works on bytes, and non-ASCII characters are multi-byte in UTF-8. So é (U+00E9) becomes two bytes, 0xC3 0xA9, encoded as %C3%A9. One character → multiple %XX triplets. An emoji like 😀 is 4 UTF-8 bytes → four triplets. The decoder reverses it: percent-decode to bytes, interpret as UTF-8.

This two-step order matters: encode the character to UTF-8 bytes first, then percent-encode each byte. Skipping the UTF-8 step (or using a legacy encoding) produces mojibake — the classic garbled-text bug.

encodeURIComponent vs encodeURI

encodeURIComponent()encodeURI()
EncodesEverything except unreserved charsOnly characters illegal in a URL
Leaves alone- _ . ~Reserved syntax chars (: / ? # & =…)
Use forQuery values, path segmentsA complete, already-structured URL

The rule of thumb: encodeURIComponent for the pieces, encodeURI for the whole. encodeURIComponent("fish & chips") → fish%20%26%20chips ✓. But encodeURIComponent("https://x.com/?q=a&b=c") mangles the structure into https%3A%2F%2F... ✗ — that's an encodeURI job (or better, build with the URL/URLSearchParams APIs and skip manual encoding entirely).

Other languages: Python's urllib.parse.quote() ≈ encodeURIComponent; PHP's rawurlencode() ≈ encodeURIComponent (urlencode() uses the +-for-space form convention instead).

Common bugs

  • Double encoding: encoding an already-encoded value turns %20 into %2520 (the % becomes %25). Symptom: literal %20 text showing in your page. Encode exactly once, at the boundary where the value enters the URL.
  • + vs %20: HTML forms encode spaces as +; RFC 3986 uses %20. In query strings most servers decode both — but in a path, + is a literal plus. If your API treats them identically everywhere, you'll eventually get bitten.
  • Base64 in query strings: standard Base64 output contains + and / — paste it raw into a URL and the + decodes as a space, corrupting the data. Use Base64URL variant (or percent-encode the Base64) for anything traveling in a URL. This is exactly why JWTs use base64url.

When to encode: the checklist

  • Query parameter values — always (?q=fish%20%26%20chips).
  • Path segments — encode the segment, not the slashes (/files/my%20report.pdf).
  • Fragment values your app parses — same rules as query values.
  • User-supplied filenames in download URLs.
  • Never: the full assembled URL, or characters serving as URL syntax.

Frequently asked questions

What does %20 mean in a URL?
A space. `%20` is the percent-encoding of byte 0x20 (decimal 32), which is the ASCII space character. Browsers display it as a space but transmit `%20`, since literal spaces aren't allowed in URLs.
What's the difference between URL encoding and Base64?
Percent-encoding escapes individual reserved characters inside text that's already text (`?` → `%3F`). Base64 converts arbitrary binary data into text-safe characters. Use percent-encoding for URL parts; use Base64 for embedding binary (like images) in text formats.
Should I encode the whole URL or just parts?
Just the parts — encode each query value and path segment separately, then assemble. Encoding a whole URL mangles its structure: `https://` becomes `https%3A%2F%2F`, breaking it. That's exactly what encodeURIComponent-vs-encodeURI is about.
Why is a space sometimes + instead of %20?
HTML form submissions historically encode spaces as `+` (application/x-www-form-urlencoded), while RFC 3986 percent-encoding uses `%20`. Most servers accept both in query strings, but `+` in a path segment means a literal plus — context matters.
How do I decode a URL?
In JavaScript: `decodeURIComponent()` (and `decodeURI()` for whole URLs). In Python: `urllib.parse.unquote()`. In PHP: `rawurldecode()` (or `urldecode()` if you need `+`-as-space form semantics). Or paste it into our URL Encoder/Decoder tool.
What characters never need encoding?
The unreserved set from RFC 3986: `A–Z a–z 0–9` plus `- _ . ~`. Everything else is either reserved (encode when used as data) or must be UTF-8-encoded first, then percent-encoded byte by byte.
Can URLs contain emoji?
Not literally — but they can effectively. Browsers UTF-8-encode the emoji and percent-encode each byte (😀 becomes 4 triplets), displaying the pretty character while transmitting the encoded form. Internationalized domain names use a separate mechanism (Punycode), not percent-encoding.

Related articles

Try the free tool