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:

FunctionLeaves aloneUse 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 and decode URLs → · Related: character encoding · Back to all articles