Syntax
encodeURIComponent(str) // → percent-encoded string
decodeURIComponent(str) // → the inverse It takes one string and returns a new one with every "unsafe" character replaced by its UTF-8 percent-escape. It never mutates the input.
What it does and doesn't escape
encodeURIComponent leaves only these 73
characters untouched:
A-Z a-z 0-9 - _ . ! ~ * ' ( )
Everything else is percent-encoded — including the URL syntax
characters that encodeURI deliberately keeps:
| Input | encodeURIComponent output |
|---|---|
/ | %2F |
? | %3F |
# | %23 |
& | %26 |
= | %3D |
+ | %2B |
: | %3A |
@ | %40 |
| space | %20 |
☃ (any non-ASCII) | %E2%98%83 (UTF-8 bytes) |
Worked examples
encodeURIComponent("hello world"); // "hello%20world"
encodeURIComponent("a/b?c=d"); // "a%2Fb%3Fc%3Dd"
encodeURIComponent("1+1=2"); // "1%2B1%3D2"
encodeURIComponent("café"); // "caf%C3%A9"
decodeURIComponent("caf%C3%A9"); // "café" The one job it's for
Use it on a value that goes inside a URL — never on the whole
URL. Encoding a value lets it contain &,
=, / or ? without the URL
parser mistaking them for structure:
const q = "shoes & socks";
const url = "https://example.com/search?q=" + encodeURIComponent(q);
// → https://example.com/search?q=shoes%20%26%20socks The canonical, harder-to-misuse way is to skip manual encoding entirely:
const u = new URL("https://example.com/search");
u.searchParams.set("q", "shoes & socks");
u.toString();
// → https://example.com/search?q=shoes+%26+socks URLSearchParams encodes a space as +
(form-style) where encodeURIComponent uses
%20; both decode back to a space on the server.
Common mistakes
Using it on a whole URL
Running encodeURIComponent over an entire URL escapes the
:, / and ? that make it a URL,
producing garbage. Use encodeURI for a full URL — or, better,
build it with new URL(). See
encodeURI vs encodeURIComponent.
Double-encoding
Encoding an already-encoded string turns every % into
%25, so %20 becomes %2520.
Encode exactly once, at the point you assemble the URL.
Expecting it to escape ! ' ( ) *
It doesn't. If a downstream consumer needs those escaped (some strict
form-encoders, OAuth 1.0 signing), escape them yourself:
encodeURIComponent(s).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase()).
Decoding untrusted input without try/catch
decodeURIComponent throws a URIError on a lone
% or a malformed escape like %ZZ. Wrap it
when the input could be hostile.
Reference
- MDN: encodeURIComponent
- encodeURI vs encodeURIComponent — when each one is the right call.
FAQ
What characters does encodeURIComponent NOT escape?
Exactly these: A-Z a-z 0-9 and the eleven characters - _ . ! ~ * ' ( ). Everything else — including / ? # & = + space and all non-ASCII — is percent-encoded as one or more %XX bytes (UTF-8).
Does encodeURIComponent escape '+'?
Yes. encodeURIComponent('+') returns '%2B'. This is the opposite of encodeURI, which leaves + untouched. A literal + is only dangerous when it reaches a form-decoding server un-escaped — and encodeURIComponent prevents exactly that.
How do I reverse it?
decodeURIComponent. It's the exact inverse, except it throws a URIError on malformed escapes (a lone % or %ZZ), so wrap it in try/catch when decoding untrusted input.
encodeURIComponent or encodeURI?
encodeURIComponent, almost always. Use it for a value going inside a URL (a query parameter or a path segment). encodeURI is only for an entire, already-structured URL, and even then new URL() + URLSearchParams is safer.
Why does encodeURIComponent leave ! ' ( ) * unescaped?
Historical accident. RFC 3986 classifies them as reserved sub-delimiters, but ECMA-262's encodeURIComponent predates that and treats them as safe. They're fine in URL paths and query values; if you embed into strict form-encoded data you may need to escape them yourself.