The two-line answer
-
encodeURI— encodes a whole URL. Leaves URL syntax characters (: / ? # [ ] @ ! $ & ' ( ) * + , ; =) alone, because the URL needs them. -
encodeURIComponent— encodes a single piece of a URL. Escapes URL syntax characters, because you're putting this inside a URL component (path segment or query parameter value).
If you don't already know your input contains a complete URL, you
want encodeURIComponent.
The character difference
The two functions agree on letters, digits, and the unreserved
characters - _ . ~. They split on the URL syntax
characters:
| Character | encodeURI | encodeURIComponent |
|---|---|---|
: | kept | %3A |
/ | kept | %2F |
? | kept | %3F |
# | kept | %23 |
& | kept | %26 |
= | kept | %3D |
+ | kept ⚠️ | %2B |
$ | kept | %24 |
, | kept | %2C |
@ | kept | %40 |
| space | %20 | %20 |
| literal Unicode | UTF-8 percent-encoded | UTF-8 percent-encoded |
The + row is highlighted because it's a footgun on its
own — see below.
The classic bug
You build a URL by string concatenation:
const q = "shoes & socks";
const url = "https://example.com/search?q=" + encodeURI(q);
// → https://example.com/search?q=shoes%20&%20socks
The & didn't get escaped. The receiving server
sees the URL as having two query parameters: q=shoes
and %20 (with no value). Your search breaks.
Fix:
const url = "https://example.com/search?q=" + encodeURIComponent(q);
// → https://example.com/search?q=shoes%20%26%20socks Or, the canonical way:
const u = new URL("https://example.com/search");
u.searchParams.set("q", q);
url.toString();
// → https://example.com/search?q=shoes+%26+socks
Note that URLSearchParams encodes spaces as +
(form-style), while encodeURIComponent uses %20.
Both are valid; servers accept either when parsing queries.
The + / space gotcha
URLs have two standards for spaces:
- Generic URL syntax (RFC 3986) uses
%20. This is whatencodeURIComponentemits. - Form-encoded data (the historical
application/x-www-form-urlencoded) uses+for a space, and%2Bfor a literal+.
The bug surfaces when a literal + reaches the query
string un-escaped and a form-decoding server reads it as a
space. encodeURIComponent is safe — it escapes
+ to %2B. encodeURI is not —
it leaves + alone:
const v = "1+1=2";
encodeURIComponent(v); // "1%2B1%3D2" ✅ the + is escaped; server sees "1+1=2"
encodeURI(v); // "1+1=2" ⚠️ the + survives; a form decoder reads "1 1=2"
So the rule holds: encode values with
encodeURIComponent (or URLSearchParams),
never with encodeURI. Frameworks that decode
+ as a space include PHP's $_GET and
Express's query parser.
The five percent-decoders, ordered
For completeness, JavaScript has these:
-
encodeURI/decodeURI— for full URLs. -
encodeURIComponent/decodeURIComponent— for URL pieces. Default to these. -
escape/unescape— deprecated. Don't use. They're not UTF-8 aware. Removed in modern strict interpretations. -
URLSearchParams— for building query strings. Most robust option for that case. -
new URL(base)— for assembling URLs. Combine withURLSearchParamsviaurl.searchParams.
Decision flowchart
-
Are you building a URL? → Use
new URL(base)and itssearchParams. Don't concatenate strings. -
Are you encoding one piece of an existing URL (a path segment, a
single query value)? →
encodeURIComponent. -
Are you encoding a complete URL whose pieces are already correct
but contains some non-ASCII characters? →
encodeURI. (Rare. Usually means a malformed URL was built upstream.) -
Are you using
escape? → Stop. It's deprecated.
Reference
- RFC 3986 — Uniform Resource Identifier: Generic Syntax. The spec the functions implement.
- MDN: URLSearchParams — modern API for query strings.
FAQ
Which one should I use 99% of the time?
encodeURIComponent. You almost never want to encode a whole URL — you want to encode a value going into a URL. The exception is constructing a full URL from a base, but even then it's safer to use the URL constructor and URLSearchParams.
Why does my '+' in a query string get parsed as a space on the server?
Form-style decoding (application/x-www-form-urlencoded) treats + as a space. encodeURIComponent is actually safe here — it escapes a literal + to %2B, so the server sees the real +. The bug comes from encodeURI (and raw string concatenation), which leave + untouched: that bare + then reaches a form-decoding server (PHP's $_GET, Express's query parser) and is read as a space. Fix: encode values with encodeURIComponent or URLSearchParams, both of which escape + correctly.
Why does encodeURIComponent leave ! ' ( ) * unescaped?
Historical accident. RFC 3986 marks them as 'reserved' (sub-delims), but ECMA-262's encodeURIComponent predates that classification. They're safe in URL paths and query string values, but if you're embedding into form-encoded data you may need to escape them yourself.
Is decodeURIComponent the inverse of encodeURIComponent?
Yes, except decodeURIComponent throws a URIError on malformed percent-escapes (like %ZZ or a lone %). Wrap in try/catch when decoding untrusted input.
Should I use URL and URLSearchParams instead?
For non-trivial cases, yes. URLSearchParams handles the + / space mismatch correctly, escapes correctly for query strings, and rejects malformed inputs. Use new URL(base) and url.searchParams.append(k, v) and you'll dodge most encoding bugs entirely.