Common URL Encoding Mistakes Developers Make
URL encoding bugs are sneaky — they often work fine in local testing and only break with specific real-world input: a redirect URL, a name with an ampersand, or a search query with special characters. Here are the mistakes that come up again and again, and how to avoid each one.
Mistake 1: double-encoding
This happens when a value gets encoded twice — often because it was already encoded upstream (e.g. by a form, a router, or a previous function call) and your code encodes it again. The % from the first pass gets re-encoded into %25.
const raw = 'hello world'; const encodedOnce = encodeURIComponent(raw); // → 'hello%20world' (correct) const encodedTwice = encodeURIComponent(encodedOnce); // → 'hello%2520world' (BUG — the % became %25) // Fix: encode exactly once, at the point where the value enters the URL. // If you're not sure whether a value is already encoded, decode first, // then encode once to normalize it: const normalized = encodeURIComponent(decodeURIComponent(encodedOnce)); // → 'hello%20world'
Mistake 2: encoding the whole URL instead of just the parameters
Running encodeURIComponent on a complete URL encodes structural characters like :, /, and ? too — turning a working URL into garbage. Only encode the dynamic parts (query values, path segments containing user input), not the whole thing.
const search = 'node.js & express';
// WRONG — encodes the entire URL, breaking :// and ?
const bad = encodeURIComponent(`https://api.example.com/search?q=${search}`);
// → 'https%3A%2F%2Fapi.example.com%2Fsearch%3Fq%3Dnode.js%20%26%20express'
// This is no longer a valid URL — it's a single encoded blob.
// RIGHT — only encode the dynamic value, keep the URL structure intact
const good = `https://api.example.com/search?q=${encodeURIComponent(search)}`;
// → 'https://api.example.com/search?q=node.js%20%26%20express'Mistake 3: forgetting to encode a redirect URL passed as a parameter
When you pass one URL as a query parameter of another (a common pattern for login redirects), the inner URL's own ? and & characters will be misread as belonging to the outer URL unless the inner URL is encoded first.
const returnTo = 'https://app.example.com/dashboard?tab=billing&ref=email';
// WRONG — the inner URL's & splits the outer query string
const bad = `https://auth.example.com/login?returnTo=${returnTo}`;
// → '...?returnTo=https://app.example.com/dashboard?tab=billing&ref=email'
// The server now sees "tab" and "ref" as SEPARATE outer params — broken.
// RIGHT — encode the inner URL before embedding it
const good = `https://auth.example.com/login?returnTo=${encodeURIComponent(returnTo)}`;
// → '...?returnTo=https%3A%2F%2Fapp.example.com%2Fdashboard%3Ftab%3Dbilling%26ref%3Demail'Mistake 4: confusing + and %20 for spaces
+ only means "space" inside application/x-www-form-urlencoded content (form submissions, and query strings built with URLSearchParams). Everywhere else — path segments, fragment identifiers, manually-built strings — + is a literal plus sign, and %20 is the only unambiguous way to encode a space.
encodeURIComponent('a+b'); // 'a%2Bb' — encodeURIComponent always escapes '+'
encodeURIComponent('a b'); // 'a%20b' — spaces become %20, not '+'
// URLSearchParams uses form-urlencoded convention: space becomes '+'
new URLSearchParams({ q: 'a b' }).toString();
// → 'q=a+b'
// If you manually decode a URLSearchParams-style string with decodeURIComponent
// instead of URLSearchParams.get(), '+' will NOT be converted back to a space:
decodeURIComponent('a+b'); // 'a+b' (WRONG if you meant 'a b')
new URLSearchParams('q=a+b').get('q'); // 'a b' (correct)Quick checklist to avoid these bugs
- Encode each dynamic value exactly once, right before inserting it into the URL — never encode a URL that might already be encoded without checking first.
- Use
encodeURIComponentfor individual values, never for a complete URL. - Always encode nested URLs (redirects, callback URLs) before embedding them as a parameter.
- Be consistent about
+vs%20— preferURLSearchParamsso the library handles the convention correctly for you. - Prefer
URL/URLSearchParamsover manual string concatenation whenever possible — they eliminate most of these mistakes by construction.
Frequently Asked Questions
Double-encoding happens when you call an encoding function on a string that is already percent-encoded. The % from the first encoding gets encoded again into %25, turning %20 into %2520 instead of staying %20 — which breaks the value when the server decodes it only once.
Never run encodeURIComponent on an entire URL — it encodes structural characters like :, /, and ? too, breaking the URL. Use encodeURI for a full URL, or better, encode only the individual dynamic parts (like query values) with encodeURIComponent before assembling the URL.
Only inside application/x-www-form-urlencoded data, such as form submissions and query strings built by URLSearchParams. Outside that context, + is a literal plus sign, not a space, and %20 is the only universally correct way to encode a space.