URL Encoding Explained: encodeURIComponent vs encodeURI (and %20 vs +)
Most URL encoding bugs share a pattern. Everything works in testing, then a real user searches for "R&D" or names their file "Q3 report #2.pdf", and something quietly breaks. Understanding what encoding is for makes these bugs easy to avoid.
Why URLs need encoding at all
A URL is a string with structure. Certain characters are part of that structure: / separates path segments, ? starts the query, & separates parameters, = separates a name from its value and # starts the fragment. If your data contains any of those characters, the browser or server can't tell whether it's data or structure.
Percent-encoding solves this by replacing the character with % and its byte value in hex. The ampersand becomes %26, so ?company=R%26D is unambiguous. Characters outside ASCII are first converted to UTF-8 bytes, which is why "é" becomes %C3%A9: two bytes, two escapes.
The two JavaScript functions
JavaScript gives you two encoders, and picking the wrong one is the most common mistake.
encodeURIComponent assumes you're encoding a piece of a URL, so it escapes everything with special meaning, including / ? & = # :. Use it for query values, path segments and anything user-supplied.
encodeURI assumes you're encoding a complete URL, so it leaves those structural characters alone and only escapes things that can never appear, such as spaces and non-ASCII characters.
const q = 'fish & chips';
encodeURI(q) // "fish%20&%20chips" -- the & survives and breaks your query
encodeURIComponent(q) // "fish%20%26%20chips" -- correct
In practice you'll almost always want encodeURIComponent, applied to each value separately before you assemble the URL. Better still, let the platform do it:
const url = new URL('https://example.com/search');
url.searchParams.set('q', 'fish & chips');
url.toString(); // https://example.com/search?q=fish+%26+chips
URLSearchParams handles every edge case for you. Notice it encoded the spaces as +, which brings us to the next question.
%20 or +?
Both can mean a space, but in different places. The + convention comes from HTML forms: when a browser submits a form using application/x-www-form-urlencoded, spaces become plus signs. That convention only applies to the query string. In the path, a plus sign is just a plus sign.
/files/Q3+report.pdfrefers to a file literally named "Q3+report.pdf"./files/Q3%20report.pdfrefers to "Q3 report.pdf".?name=Q3+reportand?name=Q3%20reportare usually both read as "Q3 report".
%20 is correct everywhere, so when you're producing URLs by hand, prefer it. When decoding query strings, treat + as a space. And if you need a literal plus sign in a query value, such as a phone number like +44, it must be encoded as %2B. Forgetting this is why international phone numbers sometimes arrive at servers with a leading space.
Double encoding
If you see %2520 in a URL, something encoded an already encoded string. % becomes %25, so %20 becomes %2520. It typically happens when one layer of code encodes a value and a library encodes it again. The fix is to encode exactly once, as late as possible, ideally by handing raw values to URLSearchParams or your HTTP client and letting it build the URL.
The opposite problem, decoding twice, is more dangerous. Some security filters check a URL for things like ../ after a single decode, while the application decodes twice. An attacker sends %252e%252e%252f and slips past. Decode once, at the boundary, and validate after decoding.
Which characters are always safe?
The "unreserved" characters never need encoding: letters A–Z and a–z, digits, and the four symbols - . _ ~. This is also why kebab-case and snake_case are popular for URL slugs. They're readable and need no escaping at all.
Quick checklist
- Encode each value with
encodeURIComponentor useURLSearchParams. - Never encode a whole URL with
encodeURIComponent. - Encode once. If you see
%25followed by two hex digits, look for double encoding. - Encode a literal
+in query values as%2B.
When you're staring at a long tracking link and want to know what's actually in it, paste it into the URL parser. It splits out every parameter, already decoded.
Percent-encode or decode text, and split URLs into their parts.
Open the URL Encoder / Decoder