URL encoding is one of those things you learn once, use constantly, and never really think about until something breaks. Then you spend an hour figuring out why + in a query string turned into a space, or why an ampersand in a search term truncated your parameters.

Let's clear it up properly.

What URL encoding actually is

URLs can only contain a limited set of characters safely. The spec (RFC 3986) defines a small group of "unreserved" characters that are always allowed as-is: letters, digits, and a handful of symbols (-, ., _, ~). Everything else either has a special meaning in a URL or isn't allowed at all.

URL encoding — also called percent-encoding — replaces those problematic characters with a % followed by their hex byte value. A space becomes %20. An ampersand becomes %26. A forward slash becomes %2F.

Hello World! → Hello%20World%21
name=John&Jane → name%3DJohn%26Jane

When you actually need it

The most common case is building query strings. If a value contains a character that means something structural in a URL — &, =, ?, # — you have to encode it, or the URL falls apart.

Say you're searching for the term "cats & dogs":

// Wrong — the & ends the parameter early
https://example.com/search?q=cats & dogs

// Right
https://example.com/search?q=cats%20%26%20dogs

Other places encoding matters:

  • Path segments that contain slashes, spaces, or non-ASCII characters.
  • Form submissions using application/x-www-form-urlencoded.
  • Redirect URLs passed as a query parameter (a URL inside a URL — a classic source of double-encoding pain).
  • OAuth and API signatures, where the exact encoding is part of the signed payload.

The gotchas that bite people

1. Spaces: %20 vs +

In the path portion of a URL, a space is %20. In a query string using the form-encoded format, a space is often +. Both are valid in the right context, but they aren't interchangeable everywhere. If you decode a + as a literal plus sign when it should have been a space, your data is subtly wrong. JavaScript's encodeURIComponent always uses %20, which is the safer default.

2. encodeURI vs encodeURIComponent

This is the JavaScript trap. encodeURI is meant for encoding a whole URL, so it leaves characters like &, =, and ? alone — because those are supposed to appear in a valid URL. encodeURIComponent encodes a single component (like one query value) and escapes those characters too.

const value = "a=1&b=2";

encodeURI(value);          // "a=1&b=2"  (broken as a param)
encodeURIComponent(value); // "a%3D1%26b%3D2"  (correct)

Rule of thumb: use encodeURIComponent for individual values you're stitching into a URL, and reserve encodeURI for cases where you already have a full URL that just needs light cleanup.

3. Double-encoding

Encode something twice and you get garbage. A space becomes %20, then the % itself gets encoded to %25, giving you %2520. This happens constantly when a value passes through multiple layers — a client encodes, a framework encodes again, a proxy re-encodes. If you see %25 showing up where you didn't expect it, you're double-encoding somewhere.

4. Unicode and bytes

Percent-encoding works on bytes, not characters. Modern URLs use UTF-8, so a character like é (which is two bytes in UTF-8) encodes to %C3%A9. Older systems that assumed one byte per character produce broken output for anything non-ASCII. Make sure everything in your stack agrees on UTF-8.

A worked example

Let's build a URL to a maps service that takes a full address as a query parameter.

const address = "10 Downing St, London SW1A 2AA";
const base = "https://maps.example.com/search";

const url = `${base}?q=${encodeURIComponent(address)}`;

console.log(url);
// https://maps.example.com/search?q=10%20Downing%20St%2C%20London%20SW1A%202AA

Notice the comma became %2C and the spaces became %20. If we'd skipped encoding, the comma and spaces would have left the URL technically malformed, and some servers would reject it outright.

Decoding on the receiving end reverses it:

const raw = "10%20Downing%20St%2C%20London%20SW1A%202AA";
decodeURIComponent(raw);
// "10 Downing St, London SW1A 2AA"

When you just need to check something

Half the time you don't need code at all — you're debugging a log line or an API response and want to see what a nasty-looking encoded string actually contains. Pasting it into a quick tool is faster than opening a REPL. I keep this URL Encoder / Decoder bookmarked for exactly that: drop in a string, see it decoded (or encoded) instantly, and spot double-encoding at a glance.

Quick reference

  • Use encodeURIComponent for individual query values and path segments.
  • Use encodeURI only for a full URL that needs escaping without breaking its structure.
  • Spaces are %20 in paths; + only in form-encoded bodies.
  • If you see %25 where you expect a real character, suspect double-encoding.
  • Keep the whole pipeline on UTF-8 to avoid mangled non-ASCII characters.

The takeaway

URL encoding isn't complicated, but it's unforgiving. The bugs it causes are silent — no exception, just a value that's slightly wrong or a parameter that mysteriously vanishes. Pick the right function for the job, encode values not whole URLs, and watch for double-encoding. Get those three right and most of the pain disappears.

Photo by Chris Ried on Unsplash