encodeURIComponent(value) percent-encodes a string so it can sit inside one part of a URL: a query value, a path segment, a fragment. Every character except A-Z a-z 0-9 - _ . ! ~ * ' ( ) becomes % plus two hex digits for each of its UTF-8 bytes, so &, =, /, ? and # in the value can’t break the URL apart.
const value = 'Tom & Jerry/50% off?';
encodeURIComponent(value); // 'Tom%20%26%20Jerry%2F50%25%20off%3F'
encodeURI(value); // 'Tom%20&%20Jerry/50%25%20off?'
encodeURIComponent('café ☕'); // 'caf%C3%A9%20%E2%98%95'
The tool below shows both lines for the same text: As one value in a URL is encodeURIComponent, As a whole URL is encodeURI.
encodeURI can’t protect a value
encodeURI is for a complete URL, so it leaves the marks that give a URL its structure, such as / ? # & = :. Used on a value, it lets an & start a new parameter:
const q = 'café & cake';
`https://example.com/search?q=${encodeURI(q)}`; // 'https://example.com/search?q=caf%C3%A9%20&%20cake'
`https://example.com/search?q=${encodeURIComponent(q)}`; // 'https://example.com/search?q=caf%C3%A9%20%26%20cake'
It also escapes every %, so an already-encoded URL gets encoded twice: encodeURI('a%20b') is 'a%2520b'.
URLSearchParams writes + for a space
new URLSearchParams({ q: value }).toString(); // 'q=Tom+%26+Jerry%2F50%25+off%3F'
const url = new URL('https://example.com/search');
url.searchParams.set('q', value);
url.href; // 'https://example.com/search?q=Tom+%26+Jerry%2F50%25+off%3F'
URLSearchParams follows the application/x-www-form-urlencoded rules of the WHATWG URL Standard: a space becomes +, and ! ' ( ) ~ are escaped too. A server that parses query strings as form data reads + back as a space; one that only calls decodeURIComponent keeps the +. Both styles are valid, so pick one per URL and decode it the same way.
The ! ’ ( ) * gap
encodeURIComponent leaves ! ' ( ) * as they are. RFC 3986 lists them as sub-delimiters, and signing schemes such as OAuth 1.0 (RFC 5849) and AWS Signature Version 4 require them encoded. MDN’s fix:
const encodeRFC3986 = (s) => encodeURIComponent(s).replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
encodeRFC3986("it's (fun)! *now*"); // 'it%27s%20%28fun%29%21%20%2Anow%2A'
Other gotchas
escape()isn’t URL encoding:escape('café')gives'caf%E9', a Latin-1 byte that servers expecting UTF-8 can’t decode.- A lone surrogate has no UTF-8 bytes, so
encodeURIComponent('\uD800')throwsURIError: URI malformed. - Encode each value exactly once, when you build the URL, and never the whole URL after joining the pieces.