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 encodeURIComponent for 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 — prefer URLSearchParams so the library handles the convention correctly for you.
  • Prefer URL / URLSearchParams over manual string concatenation whenever possible — they eliminate most of these mistakes by construction.

Frequently Asked Questions

What is double-encoding and why is it a bug?

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.

Should I use encodeURI or encodeURIComponent on a full URL?

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.

Is + the same as %20 for encoding a space in a 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.

Try the Free URL Encoder

Not sure if a value is already encoded? Paste it into our free online tool to check and fix it before it causes a bug in production.

Related articles