Every developer has hit this bug: you pass a search query to an API, and it works perfectly until someone searches for "Tom & Jerry." The ampersand splits your query parameter in half. The server receives q=Tom and a mysterious second parameter called Jerry.

URL encoding exists to prevent exactly this kind of chaos.

1. The Anatomy of a URL

Before we can understand encoding, we need to understand what a URL is made of. RFC 3986 defines the structure:

https://api.example.com:8080/v2/search?q=hello+world&lang=en#results
\___/   \_______________/\___/\________/ \__________________/ \_____/
scheme       host        port   path        query string      fragment

Each part has its own rules about which characters are allowed. The ? separates the path from the query. The & separates query parameters. The # marks the fragment. The / separates path segments.

These characters are called reserved characters. If your data contains one of them, the URL parser can't tell the difference between structure and content. That's where percent-encoding comes in.

2. How Percent-Encoding Works

Percent-encoding replaces unsafe characters with a % followed by two hexadecimal digits representing the character's ASCII (or UTF-8) byte value.

  • Space → %20 (ASCII 32 = hex 20)
  • &%26 (ASCII 38 = hex 26)
  • =%3D (ASCII 61 = hex 3D)
  • #%23 (ASCII 35 = hex 23)
  • /%2F (ASCII 47 = hex 2F)

So "Tom & Jerry" in a query parameter becomes Tom%20%26%20Jerry. The URL parser now sees the %26 as data, not as a parameter separator.

What About the + Sign?

In HTML form submissions (application/x-www-form-urlencoded), spaces are encoded as + instead of %20. Both are valid, but they belong to different specs. %20 is the RFC 3986 standard. + is the HTML form convention. Most servers accept both, but it's a common source of double-encoding bugs.

3. encodeURI vs. encodeURIComponent

JavaScript gives you two encoding functions, and using the wrong one is one of the most common web development mistakes.

encodeURI()

Encodes a full URL. It leaves structural characters alone (: / ? # & =) because it assumes they're part of the URL's structure.

encodeURI("https://example.com/path?q=hello world")
// "https://example.com/path?q=hello%20world"
// ✅ The :// and ? are preserved

encodeURIComponent()

Encodes a single value within a URL. It encodes everything except letters, digits, and - _ . ~ .

encodeURIComponent("hello world&foo=bar")
// "hello%20world%26foo%3Dbar"
// ✅ The & and = are escaped because they're DATA, not structure

The Golden Rule

Building a full URL from scratch? Use encodeURI(). Inserting a user-provided value into a query parameter? Always use encodeURIComponent().

// ❌ WRONG: encodeURI won't escape the & in the user's input
const url = encodeURI(`https://api.com/search?q=${userInput}`);

// ✅ RIGHT: encode just the value
const url = `https://api.com/search?q=${encodeURIComponent(userInput)}`;

4. Unicode and Internationalized URLs

ASCII only covers 128 characters. What happens when someone searches for "café" or "日本語"?

The character is first converted to its UTF-8 byte sequence, and then each byte is percent-encoded individually:

  • "é" → UTF-8 bytes: C3 A9%C3%A9
  • "日" → UTF-8 bytes: E6 97 A5%E6%97%A5

Modern browsers hide this complexity by displaying decoded Unicode in the address bar while sending the encoded version over the wire. But if you're logging URLs or building them manually, you need to be aware of this multi-byte encoding.

5. Common Pitfalls

Double Encoding

If you encode a value that's already encoded, %20 becomes %2520 (the % itself gets encoded to %25). The server receives a literal %20 string instead of a space. Always encode raw values, never pre-encoded strings.

Forgetting to Encode Path Segments

Most developers remember to encode query parameters but forget that path segments can contain special characters too. A file named my report (final).pdf needs encoding in the path:

/files/my%20report%20(final).pdf

API Keys in URLs

If your API key contains + or / (common in Base64-encoded keys), they must be percent-encoded when placed in a URL. Otherwise the / creates a false path segment and the + becomes a space.

6. Building Query Strings Safely with URLSearchParams

Manually constructing query strings with string concatenation is error-prone — it is easy to forget to encode a value, double-encode an already-encoded one, or misplace an ampersand. The URLSearchParams API handles all of this correctly:

// Manual — fragile, easy to introduce bugs
const url = `https://api.com/search?q=${userQuery}&lang=${lang}&page=${page}`;

// URLSearchParams — handles encoding automatically
const params = new URLSearchParams({
    q: userQuery,   // automatically encodes spaces, &, =, etc.
    lang: lang,
    page: page
});
const url = `https://api.com/search?${params.toString()}`;

// Also useful for reading query strings
const input = "?q=Tom+%26+Jerry&lang=en";
const parsed = new URLSearchParams(input);
parsed.get('q');   // "Tom & Jerry" — decoded automatically
parsed.get('lang'); // "en"

URLSearchParams is available natively in all modern browsers and Node.js 10+. It correctly handles the + vs %20 space convention in query strings, serialises arrays, and decodes percent-encoded values on read. For any new code that constructs or parses query strings, prefer URLSearchParams over manual string manipulation.

7. Query String Arrays: The Framework Problem

There is no RFC standard for encoding arrays in query strings. Different frameworks have adopted incompatible conventions, which causes silent bugs when sending multi-value parameters to an API that expects a different format.

Convention Format Used By
Repeated keys ids=1&ids=2&ids=3 URLSearchParams, Java Servlet, Go
Bracket notation ids[]=1&ids[]=2 PHP, Rails, jQuery.param()
Comma-separated ids=1,2,3 Many REST APIs, OpenAPI
Index notation ids[0]=1&ids[1]=2 ASP.NET, some PHP frameworks

When integrating with a third-party API, always check its documentation for the expected array format. When building your own API, document which convention you use. The repeated keys format is most broadly supported by standard parsers and is what URLSearchParams.append() produces by default.

Frequently Asked Questions

Why does + sometimes mean a space and sometimes not?

In query strings (the part after ?), + means a space under the application/x-www-form-urlencoded convention — the encoding used by HTML form submissions. This is a legacy convention from early web forms. In URL path segments (before the ?), + is a literal plus sign and must be written as %2B if you mean a plus in data. Outside query strings, always use %20 for spaces. URLSearchParams handles this context correctly when you use toString().

How do I detect double-encoding in a URL?

If you see %25 in a URL where you expected %, the value has been double-encoded — the percent sign itself was encoded. You can also look for sequences like %2520 (double-encoded space). The simplest fix is to ensure encoding happens exactly once, on the raw value before any concatenation. If you are receiving URLs from external sources, decode once with decodeURIComponent() and check whether the result still contains percent-encoded sequences.

Should API keys be placed in the URL?

Not if you can avoid it. URL parameters are logged in server access logs, browser history, proxy logs, and HTTP Referer headers — all places where secrets should not appear. Prefer sending API keys in the Authorization header or a custom header (both are excluded from most access logs and Referer leakage). If you must include a key in a URL (some APIs require it), ensure it is percent-encoded if it contains any Base64 characters like + or /.

Conclusion

URL encoding is invisible when it works and catastrophic when it doesn't. The rules are simple: encode values with encodeURIComponent(), never double-encode, and test with special characters like & = + # / and Unicode.

Need to debug a messy URL? Use our URL Encoder to encode, decode, and parse query strings instantly in your browser.