URL Encoding: Common Pitfalls and How to Avoid Them
URL encoding is one of those topics every developer thinks they understand — until they spend three hours debugging why a query string with an ampersand keeps breaking the API. This guide covers the surprising depth of URL encoding in 2026.
The basics: what URL encoding does
URLs can only contain a specific subset of ASCII characters. Anything outside (or special structural characters like ?, &, =, /, #) must be percent-encoded as % followed by the hex byte value.
Examples:
space→%20(or+in query strings)&→%26=→%3D/→%2F#→%23
The encodeURI vs encodeURIComponent confusion
JavaScript has two functions, and using the wrong one causes the most common URL encoding bug:
| Function | Encodes | Use for |
|---|---|---|
encodeURI() | spaces, special chars in URL | Encoding a complete URL |
encodeURIComponent() | everything except a-z, 0-9, -, _, ., ~ | Encoding query parameter values |
The crucial difference: encodeURI does NOT encode ?, &, =, / — because these are structural URL characters. encodeURIComponent encodes EVERYTHING that isn't unreserved.
The classic bug
// WRONG - encodeURI doesn't encode &consturl ='https://api.com?q='+encodeURI('tom & jerry');// Result: https://api.com?q=tom%20&%20jerry// API sees q=tom and a separate empty param!// RIGHT - encodeURIComponent encodes &consturl ='https://api.com?q='+encodeURIComponent('tom & jerry');// Result: https://api.com?q=tom%20%26%20jerry// API sees q="tom & jerry" - correct!
Use URLSearchParams instead
Modern browsers and Node have URLSearchParams which handles encoding correctly:
constparams =newURLSearchParams({ q:'tom & jerry', category:'cartoons/animation'});consturl =`https://api.com?${params}`;// Result: https://api.com?q=tom+%26+jerry&category=cartoons%2Fanimation
This is the preferred approach in 2026 — it never encodes structural characters incorrectly and it handles edge cases.
Spaces: %20 vs + (the historical mess)
In query strings, spaces can be encoded two ways:
%20— RFC 3986 standard, valid anywhere in URL+— application/x-www-form-urlencoded standard, valid in query string only
Both work for query strings on virtually all servers. encodeURIComponent outputs %20; URLSearchParams outputs +. Both decode correctly.
For path segments, only %20 is correct — + stays as a literal plus.
Double encoding
The "I encoded it but the server encoded it again" problem:
// You: encode "tom & jerry"// Result: tom%20%26%20jerry// Server framework auto-encodes the param again// Result: tom%2520%2526%2520jerry// Now decoded back: tom%20%26%20jerry (still encoded!)
Diagnosis: see %25 in the URL where you didn't put it. %25 is the encoding of % itself, so it's a smoking-gun sign of double encoding.
Unicode and the multi-byte trap
URL encoding works on bytes, not characters. UTF-8 multi-byte characters become multiple percent escapes:
"चाय" (Hindi: tea) UTF-8 bytes: E0 A4 9A E0 A4 BE E0 A4 AF Encoded: %E0%A4%9A%E0%A4%BE%E0%A4%AF
JavaScript's encodeURIComponent does this correctly. Other languages may need explicit UTF-8 conversion first.
What you don't need to encode
RFC 3986 "unreserved" characters — never encoded:
A-Z a-z 0-9- _ . ~
"Reserved" characters that should be encoded if they appear in a value (not as a delimiter):
- General:
: / ? # [ ] @ - Sub-delims:
! $ & ' ( ) * + , ; =
URL parts and their encoding rules
| Part | Example | Encoding |
|---|---|---|
| Scheme | https | Never needs encoding |
| Host | api.example.com | IDN punycode for non-ASCII |
| Path | /users/123 | Encode special chars within segments, not / |
| Query | ?q=hello | Use URLSearchParams |
| Fragment | #section | Encode special chars |
IDN: Internationalized Domain Names
Domain names with non-ASCII (e.g., ऒग-ङ.जठ) use Punycode encoding:
- The Hindi domain becomes
xn--p1bd0aud.xn--11b7cin DNS - Browsers display the original Unicode
- HTTP libraries usually handle this transparently
Decoding pitfalls
- Decoding before splitting. If you decode
?q=tom%26jerrybefore splitting on&, the%26becomes&and you split incorrectly. - Trusting decoded input. A decoded URL parameter can contain anything — XSS, SQL injection, path traversal. Always validate after decoding.
- Decoding twice. If your framework already decodes, decoding again corrupts data.
Tools
- URL encoder/decoder — quickly encode/decode strings
- Base64 — different encoding (not for URLs by default)
Bottom line
Use URLSearchParams in 2026 — it handles encoding correctly. If you must use encodeURIComponent, never use it on a full URL — only on individual values. Watch for %25 in URLs as a sign of double encoding. Validate every decoded value before use.
Try the free dev tools suite
15+ tools. 100% client-side. No signup. No tracking.
Browse all tools →