The Complete Guide to URL Encoding: When to Use encodeURIComponent (and When Not To)
2026-08-06
Type a URL containing spaces or non-English characters into a browser address bar, hit enter, and watch it transform into something like %E4%B8%AD%E6%96%87. That’s URL encoding — also called percent-encoding — and almost every web developer has been bitten by it at some point: query parameters arriving as garbled text on the server, stray %25 sequences from decoding twice, or a single & character wrecking an entire parameter list. This guide explains how URL encoding actually works and gives you a clear rulebook: when to encode, which function to use, and when to leave things alone.
Why URLs Need Encoding at All
URLs were designed to be written and transmitted using the plain ASCII character set. But a URL has to do two jobs at once: it contains structural characters (:, /, ?, &, =, #) that give it meaning, and it carries arbitrary data. The conflict is obvious — if a parameter value itself contains an &, how does a parser know whether it’s a separator or part of the data? Encoding resolves this by replacing special characters inside the data with a % followed by two hexadecimal digits, so they can no longer be misread as structure.
Reserved vs. Unreserved Characters
RFC 3986 splits characters into two camps. Unreserved characters — letters, digits, and -, _, ., ~ — are safe anywhere in a URL and never need encoding. Reserved characters — : / ? # [ ] @ plus ! $ & ' ( ) * + , ; = — have structural meaning, so they must be encoded when they appear as data, but left untouched when they’re doing their structural job.
Notice that the rule is “encode by role, not by character.” The same / is a legitimate path separator that should stay as-is, yet the moment it shows up inside a query parameter value it has to become %2F. That distinction is exactly why JavaScript has two different encoding functions.
Why Query Parameters Must Be Encoded
A concrete example. Say you want to build this search link:
https://example.com/search?q=Tom & Jerry&page=1
Two things are wrong here: spaces are illegal in URLs, and the & will be parsed as a parameter separator. The server receives q=Tom, an empty parameter named Jerry, and page=1 — nothing like what you intended. Encode the value and the problem disappears:
https://example.com/search?q=Tom%20%26%20Jerry&page=1
The space becomes %20, the & becomes %26, and the parser correctly reconstructs Tom & Jerry. If you want to sanity-check an encoded result quickly, paste the text into the URL Encoder — it offers both “full URL” and “component” modes, which map directly onto the two functions discussed next.
encodeURI vs. encodeURIComponent
JavaScript ships two encoding functions, and the difference fits in one sentence: encodeURI encodes a whole URL; encodeURIComponent encodes one piece of a URL.
encodeURI assumes you’re handing it a complete URL, so it deliberately leaves : / ? & = # alone. encodeURIComponent assumes it’s getting a single value, so it encodes nearly everything except the unreserved characters:
encodeURI('https://example.com/search?q=Tom & Jerry');
// https://example.com/search?q=Tom%20&%20Jerry
// The structural ? & = survive — but so does the & inside the value. Still broken!
encodeURIComponent('Tom & Jerry');
// Tom%20%26%20Jerry
The rule of thumb: always use encodeURIComponent when building query strings:
const url = `https://example.com/search?q=${encodeURIComponent(keyword)}&page=${page}`;
encodeURI is only appropriate in one situation: you already hold a fully assembled, structurally correct URL and just want to clean up non-ASCII characters and spaces. An even better modern approach is URLSearchParams, which handles encoding for you:
const params = new URLSearchParams({ q: 'Tom & Jerry', page: '1' });
params.toString(); // q=Tom+%26+Jerry&page=1
%20 or +: A Short History Lesson
You may have noticed that URLSearchParams encodes the space as +, while encodeURIComponent produces %20. Both are correct — they just come from different specifications. The + convention comes from the early HTML form encoding format, application/x-www-form-urlencoded (what a GET form submission produces), and query strings inherited that tradition. %20 is the standard RFC 3986 percent-encoding, valid anywhere in a URL.
The practical consequence is on the decoding side: + means “space” only in a query string or form-data context — in a path, + is a literal plus sign. So for file-download links where the filename contains spaces, %20 is the safer choice; inside query parameters either form works and virtually every server accepts both. URLSearchParams and the URL object handle both forms correctly, which is one more reason to use them instead of hand-assembling strings.
Double Encoding: The Classic Trap
Double encoding happens when text that’s already percent-encoded gets encoded again. The % in %20 becomes %25, turning a space into %2520. The server decodes once and gets %20 instead of a space — the root cause behind countless garbled pages, 404s, and failed signature verifications.
Three scenarios trigger it most often: the frontend encodes once and a framework (certain routing libraries, for instance) encodes again automatically; an already-encoded URL taken from location.href is fed wholesale into encodeURIComponent; or a proxy layer like Nginx or a CDN performs a “helpful” normalization pass. Debugging is easy: search the final outgoing URL for %25 — if it’s there, something encoded twice. The fix is just as simple in principle: encode exactly once, at the last step, and pass the raw string around everywhere upstream. When you’re handed a suspicious URL, the URL Parser breaks it down into scheme, host, path, and query parameters so you can see at a glance which segment still contains stray % sequences.
Handling Non-ASCII URLs (Chinese, Emoji, and Friends)
Non-ASCII characters have no legal form in a URL. They must first be converted to a UTF-8 byte sequence, then each byte gets percent-encoded. Since a Chinese character typically takes three UTF-8 bytes, 中 becomes %E4%B8%AD. Modern browser address bars do this automatically — you can type a Chinese URL and it just works — but when your code makes the request, you’re on your own. Same rule as always: encodeURIComponent for values, or let URLSearchParams handle it.
A related case is internationalized domain names. Those don’t use percent-encoding at all; they’re converted to ASCII via Punycode (the xn-- prefix you sometimes see), which is a DNS-level mechanism that doesn’t interfere with path encoding.
Finally, don’t confuse URL encoding with Base64 — they solve different problems. URL encoding answers “can this character live inside a URL,” while Base64 answers “how do I represent binary data as printable text.” Sometimes you stack them: Base64-encode a chunk of JSON first (the Base64 Encoder/Decoder is handy for experimenting), then URL-encode the +, /, and = characters that Base64 can produce before placing the result in a query parameter. Once you understand what each layer is for, you’ll never mix them up again.
Quick Reference
- Building query strings: use
URLSearchParams, or manuallyencodeURIComponentevery value; - Cleaning up a complete URL:
encodeURI— but only if each value inside it was already encoded; - Decoding: prefer
decodeURIComponenton individual values rather thandecodeURIon the whole URL; - Spotting
%25means double encoding — find the layer that encoded one time too many; - Spaces in file names and paths: use
%20, never+.