URL Encoding URL Encoding / Percent-Encoding Encoding Schemes Flagship
The scheme that makes URLs safe to transmit by escaping reserved and non-ASCII characters as %XX sequences — and the source of the classic '+' vs '%20' space confusion.
What it is
URL encoding (formally percent-encoding, defined in RFC 3986) is the scheme that makes arbitrary text safe to include inside a URL. URLs can only reliably contain a limited set of characters — letters, digits, and a handful of punctuation marks (- _ . ~ are explicitly "unreserved" and never need encoding). Everything else — spaces, non-ASCII characters, and reserved punctuation used with special meaning in URL syntax (? & = / # etc.) — gets replaced with a % followed by the character's two-digit hexadecimal byte value.
Unlike Base64, which transforms an entire payload uniformly, URL encoding is selective: it leaves safe characters untouched and only escapes the specific bytes that would otherwise be ambiguous or break URL parsing. You'll find this tool already built on this site at URL encode/decode.
Byte structure
| Character type | Encoding |
|---|---|
Unreserved: A-Z a-z 0-9 - _ . ~ |
Left as-is, never encoded |
Reserved with special meaning: : / ? # [ ] @ ! $ & ' ( ) * + , ; = |
Encoded when used literally (not as syntax) — e.g., a literal & inside a query parameter value must be encoded as %26, or it'll be misread as a parameter separator |
| Space | %20 (percent-encoding standard) or + (older application/x-www-form-urlencoded convention — see below) |
| Non-ASCII characters (é, 中, 🎉, etc.) | UTF-8-encode the character first, then percent-encode each resulting byte — e.g., é (UTF-8: 0xC3 0xA9) becomes %C3%A9 |
That last row is important: URL encoding operates on bytes, not characters directly. Non-ASCII text is first converted to UTF-8 bytes, and then each byte gets its own %XX escape — which is why a single accented character can expand into multiple %XX sequences.
Why your URL is broken (symptom → cause)
| Symptom | Likely cause |
|---|---|
%20 appears instead of expected + (or vice versa) in a query string |
Two different conventions for encoding spaces: RFC 3986 percent-encoding uses %20; the older HTML form-submission convention (application/x-www-form-urlencoded) uses + — both are valid in the right context, but mixing them up breaks decoding |
Decoded text shows %25 where a literal % was expected |
Correct behavior if the % itself needed encoding — % is encoded as %25 since it's the escape character itself and would otherwise be ambiguous |
Double-encoded gibberish like %2520 instead of %20 |
The string was percent-encoded twice — once correctly, then encoded again by a second layer of code that didn't realize it was already encoded |
Accented/non-ASCII characters produce multiple %XX sequences per character |
Expected behavior — non-ASCII characters are UTF-8-encoded first (often 2-4 bytes), then each byte is separately percent-encoded, so one visible character can become several %XX groups |
Parameter value gets truncated at an & or = |
A literal & or = inside a value wasn't encoded, so the URL parser misread it as a new parameter boundary instead of literal content |
Compatibility & gotchas
%20vs+for spaces is a genuine, persistent ambiguity. RFC 3986 (the general URI spec) always uses%20. Theapplication/x-www-form-urlencodedconvention (used for HTML form submissions and query strings originating from forms) uses+instead — and critically, in that context, a literal+in the original data must itself be encoded as%2Bto avoid being misread as a space. Mixing these two contexts up is one of the most common URL-encoding bugs.- Double-encoding is a common and sneaky bug. If a string is percent-encoded once, then passed through an encoding function again somewhere downstream (a common issue when multiple layers of a web framework/proxy each try to "help"), the literal
%characters from the first encoding pass get re-encoded as%25, producing sequences like%2520instead of the intended%20. - Not all "unsafe" characters actually need encoding in every context. Characters like
/and?are only reserved within specific parts of a URL — a/inside a path segment vs. a/inside a query parameter value has different encoding requirements, which trips people up when writing manual encoding logic instead of using a library function. - JavaScript has two different encoding functions with different scopes:
encodeURIComponent()encodes more aggressively (safe for encoding an individual query parameter value), whileencodeURI()leaves reserved URL-structure characters like/,?,&untouched (intended for encoding a whole URL, not a single value). Using the wrong one for the context is a frequent source of bugs. - IDN (internationalized) domain names don't use percent-encoding for the domain portion — that's handled separately by Punycode, while percent-encoding covers the path, query string, and fragment.
Fixing it
- Encoding a single value for a query parameter: use your language's component-level encoder —
encodeURIComponent()in JS,urllib.parse.quote()in Python,rawurlencode()in PHP — rather than hand-rolling replacement logic. - Fixing double-encoding: decode once to check whether the string still contains
%XXpatterns after decoding; if so, it was encoded multiple times and needs decoding again until it stabilizes (though it's better to fix the root cause — find and remove the redundant encoding step in the pipeline). - Spaces in form-submitted data: if you're specifically handling
application/x-www-form-urlencodeddata (classic HTML form POST/GET),+for space is correct and expected; for general URI encoding (paths, most modern API query strings), prefer%20. - Debugging a broken parameter: percent-decode the full URL manually and check whether any literal
&,=, or%characters appear inside what should be a single value — if so, that value wasn't encoded correctly before being inserted into the URL.
Comparison
| Scheme | Key difference from URL encoding |
|---|---|
| Base64 | Transforms an entire binary payload uniformly into a fixed alphabet, rather than selectively escaping only unsafe characters — see the Base64 page |
| Punycode | Handles internationalized domain names specifically (the host portion of a URL), a completely separate mechanism from percent-encoding the path/query/fragment — see the Punycode page |
| HTML entity encoding | A different escaping scheme entirely, for safely embedding characters in HTML content (&, <) rather than in URLs — the two are often confused but serve different layers of a web stack |
FAQ
Why do I sometimes see + and sometimes %20 for spaces in URLs?
Two different, both-valid conventions exist for different contexts. General URI encoding (RFC 3986) always uses %20. The application/x-www-form-urlencoded format — used specifically for HTML form submissions and their resulting query strings — uses + instead. Mixing the two conventions in the wrong context is the source of most space-related encoding bugs.
What does %XX actually mean in a URL?
It's a percent-encoded byte: the % marks an escape sequence, and the two following characters are a hexadecimal representation of one byte's value (0–255, so 00–FF in hex). Non-ASCII characters get UTF-8-encoded first, then each resulting byte gets its own %XX sequence — so a single accented letter or emoji can produce multiple %XX groups in a row.
Why does my URL contain %2520 instead of %20?
This is a double-encoding bug — the string was percent-encoded once (producing %20), then passed through a second encoding step that treated the literal % character as needing its own escaping, turning it into %25, which combined with the remaining 20 produces %2520. Find where the redundant second encoding pass happens and remove it.
Should I use encodeURI or encodeURIComponent in JavaScript?
Use encodeURIComponent() when encoding a single value that will be inserted into a URL (a query parameter value, a path segment) — it encodes more aggressively, including characters like & and = that would otherwise break URL structure. Use encodeURI() only when encoding an entire, already-structured URL where you want to preserve characters like /, ?, and & as actual URL syntax rather than literal content.
Fun fact
The application/x-www-form-urlencoded format's use of + for spaces predates the general URI percent-encoding standard's more consistent %20 convention — it traces back to early HTML form-handling conventions from the mid-1990s, and the two standards were never fully unified, which is why the ambiguity persists in web development three decades later rather than being a simple historical footnote.