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 | kept ⚠️ |
$ | 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 value containing a literal + is
sent through encodeURIComponent (which doesn't escape
+) and read by a server that treats + as a
space:
const v = "1+1=2";
encodeURIComponent(v); // "1%2B1%3D2" — wait, no
// actually it's "1%2B1%3D2"
OK, that one's fine — encodeURIComponent escapes
=. But test on the destination server. Many frameworks
(PHP's $_GET, Express's query parser) decode
+ as a space. If you stick a literal + into
a URL via direct string concat, watch out.
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 encoding (application/x-www-form-urlencoded) treats + as a space. encodeURIComponent does NOT escape +, so a literal + in your value will be misinterpreted by servers parsing form data. Workaround: encodeURIComponent(value).replace(/%20/g, '+').replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A') — or use URLSearchParams which gets this right.
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.