URL encode in JavaScript

encodeURIComponent(value)

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') throws URIError: URI malformed.
  • Encode each value exactly once, when you build the URL, and never the whole URL after joining the pieces.

Try it in the URL Encoder & Decoder

As one value in a URL

Escaped the way encodeURIComponent does: the marks that mean something in a URL, like &, = and /, become escapes too.

As a whole URL

Escaped the way encodeURI does: the marks that shape the URL itself, like ://, ? and &, stay readable.

Runs in your browser. Nothing you enter is sent anywhere.