URL encoding is one of those things you use every day without thinking about it — until a customer name with an ampersand breaks a search link, or a base64 token in a query string mysteriously corrupts on the way to your API. Then you spend an afternoon squinting at %20 and %2B wondering where it all went wrong.

Let's clear it up properly.

What URL encoding actually is

URLs are only allowed to contain a limited set of ASCII characters. Letters, digits, and a handful of symbols like -, ., _, and ~ are safe. Everything else — spaces, slashes in the wrong place, non-ASCII text, reserved characters like ?, &, =, # — has to be escaped when it appears somewhere it doesn't belong.

The escaping scheme is percent-encoding: take the byte, write it as its hexadecimal value, and prefix it with a %. A space becomes %20. An ampersand becomes %26. Because it operates on bytes, multi-byte UTF-8 characters produce multiple percent codes — é encodes to %C3%A9.

Reserved vs. unreserved characters

The important distinction is context. Characters like &, =, and / aren't illegal in a URL — they have structural meaning. The problem starts when you want one of them as literal data rather than structure. If a query parameter value contains an ampersand, you must encode it, otherwise the parser thinks a new parameter has begun.

  • ?q=fish & chips → broken, splits into two params
  • ?q=fish%20%26%20chips → correct, one value

When and why you'll reach for it

The most common cases:

  • Building query strings by hand — user input in search links, filters, tracking parameters.
  • Passing tokens or IDs that may contain +, /, or = (base64 is a repeat offender).
  • Non-ASCII paths — international file names or slugs.
  • Redirect URLs inside URLs — an OAuth redirect_uri parameter often carries a whole nested URL, which absolutely must be encoded.

The gotchas that waste real hours

1. Space becomes + in form data, %20 elsewhere

This is the classic. In application/x-www-form-urlencoded bodies (and historically in query strings from HTML forms), a space is encoded as +. In the path segment of a URL, a space must be %20 — a literal + there means a plus sign. Different JavaScript functions follow different rules:

encodeURIComponent("a b");  // "a%20b"
new URLSearchParams({x:"a b"}).toString(); // "x=a+b"

Both are valid, just for different targets. Mixing them up is how you end up with literal plus signs in someone's name.

2. encodeURI vs. encodeURIComponent

They are not interchangeable. encodeURI is for encoding a whole URL and deliberately leaves reserved characters like &, ?, and / alone. encodeURIComponent is for a single piece — a parameter value — and encodes those reserved characters too.

const value = "a/b?c=d";
encodeURI(value);           // "a/b?c=d"  (untouched!)
encodeURIComponent(value);  // "a%2Fb%3Fc%3Dd"

Rule of thumb: if you're inserting one value into a URL you're building, use encodeURIComponent. Reach for encodeURI almost never.

3. Double encoding

Encode a value that's already encoded and you get %2520 — a percent-encoded percent sign. This happens when one layer encodes, then a framework or proxy encodes again. If you see %25 showing up in your data, suspect a double-encode somewhere in the chain.

4. Encoding the whole URL instead of the parts

Never percent-encode a complete URL as one blob. Encode each piece as you assemble it. Encode the path segments, encode each query value individually, then join with the structural characters.

A short worked example

Say you're building a link to a maps search for "Café René & Co" at a specific location.

const query = "Café René & Co";
const lat = 51.5074, lng = -0.1278;

const url = `https://example.com/search`
  + `?q=${encodeURIComponent(query)}`
  + `&lat=${lat}&lng=${lng}`;

// https://example.com/search?q=Caf%C3%A9%20Ren%C3%A9%20%26%20Co&lat=51.5074&lng=-0.1278

Notice what happened:

  • The accented characters became UTF-8 percent codes (%C3%A9).
  • The literal ampersand in the name became %26, so it doesn't collide with the & separating parameters.
  • The structural & between q, lat, and lng stays as a plain ampersand.

Decoding is the reverse: decodeURIComponent turns %C3%A9 back into é. If you paste a mangled URL into the console and can't tell where the encoding went sideways, it helps to run it through a tool that shows the before-and-after. Our URL Encoder / Decoder does exactly that — paste a string, see the encoded and decoded versions instantly, which is handy for debugging that one broken redirect_uri without writing a scratch script.

Practical advice

  • Prefer built-in URL builders. In JavaScript, use the URL and URLSearchParams objects. They handle encoding correctly and stop you concatenating strings by hand: const u = new URL("https://example.com/search"); u.searchParams.set("q", query);
  • Encode as late as possible, decode as early as possible. Keep raw values in your code and only encode at the moment you serialize into a URL. This prevents accidental double-encoding.
  • Watch the boundaries. Every hop — browser, load balancer, framework router, logging middleware — can decode or re-encode. Know which layer owns the transformation.
  • Test with the nasty inputs. Spaces, ampersands, plus signs, slashes, emoji, and already-encoded strings. If your form survives a+b & c/d 100%, it'll survive most real input.

The takeaway

URL encoding is bytes-to-percent-codes, nothing more mysterious than that. The friction comes entirely from context: is this character structure or data, form-encoded or path-encoded, encoded once or twice? Get in the habit of encoding individual components rather than whole URLs, lean on the standard library, and keep a decoder handy for when something inevitably shows up as %2520.

Photo by Pankaj Patel on Unsplash