URL Encoding: Which Function, and Where
Different parts of a URL have different rules. Using one escaping function everywhere is how links break.
URLs may only contain a restricted set of ASCII characters. Everything else — spaces, accents, ampersands that are part of a value rather than a separator — must be percent-encoded: each byte written as % followed by two hex digits.
A space becomes %20. An ampersand becomes %26. A character outside ASCII is encoded as its UTF-8 bytes, so é becomes %C3%A9 — two bytes, two escapes.
The part everyone gets wrong
A URL has parts, and the characters that are reserved differ between them. / is a separator in a path and ordinary data in a query value. & separates parameters in a query and is ordinary data in a path segment.
That is why JavaScript has two functions and why choosing the wrong one is the standard bug:
| Function | Leaves alone | Use for |
|---|---|---|
| encodeURI | : / ? # [ ] @ ! $ & ' ( ) * + , ; = | A whole URL you are not assembling |
| encodeURIComponent | - _ . ! ~ * ' ( ) | Any single value going into a URL |
Almost always you want encodeURIComponent, because almost always you are inserting one value into a URL you are building. Using encodeURI on a query value leaves & and = intact, so a value containing them silently becomes extra parameters — which is parameter injection, not just a display bug.
The plus-sign problem
In application/x-www-form-urlencoded — what HTML forms submit — a space is encoded as +, not %20. In the rest of a URL, + is a literal plus.
So ?q=a+b means "a b" when read as form data and "a+b" when read as a path. This is exactly how email addresses containing + get mangled into spaces, and why encodeURIComponent encodes + as %2B. If you are building a query string, use URLSearchParams and let it decide.
Double encoding
Encoding an already-encoded string turns %20 into %2520, because the % itself gets escaped. It usually happens when a value passes through two layers that each helpfully encode it.
The symptom is literal %20 appearing in a rendered page or a filename. The cure is knowing exactly which layer encodes — decoding twice to "fix" it creates a security problem, because a validator checking the once-decoded form can be bypassed by something that only becomes dangerous after the second pass.
Internationalised domains
Host names are not percent-encoded. Non-ASCII domains use Punycode instead: münchen.de becomes xn--mnchen-3ya.de. This is a separate mechanism from percent-encoding and applies only to the host portion.
Rules that hold
- Encode each component as you insert it, never the assembled URL.
- Use the platform's URL builder —
URLSearchParams,http.build_query— rather than concatenating strings. - Decode exactly once, at the boundary where the value enters your code.
- Validate after decoding, never before.
Encode and decode URLs → · Related: character encoding · Back to all articles