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() | |
|---|---|---|
| Encodes | Everything except unreserved chars | Only characters illegal in a URL |
| Leaves alone | - _ . ~ | Reserved syntax chars (: / ? # & =…) |
| Use for | Query values, path segments | A 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
%20into%2520(the%becomes%25). Symptom: literal%20text 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
Base64 Encoding Explained: How It Works & When to Use It
What base64 really does, why it inflates data by 33%, base64 vs base64url, and when to reach for it — and when not to.
Web DevelopmentUUID v4 vs v7: Which Version Should You Use?
Random vs time-ordered UUIDs: how v7 fixes database index fragmentation, and when v4 is still the right call.
Web DevelopmentRegex Cheat Sheet: Patterns, Flags & Examples
The regex syntax you actually use — character classes, quantifiers, groups, anchors, and flags — with copy-paste examples and honest caveats.