URL Encoding in JavaScript
Part of the Encoding ToolkitencodeURIComponent(value)the right default for a single value like a query parameter — reach for encodeURI only on an already-assembled URI, and never escape()
Explanation
JavaScript actually ships three different functions people reach for here, and they don't agree with each other. Picking the wrong one is where most "my URL works locally but breaks in production" bugs in this category come from.
encodeURIComponent — for a single value
Use this for anything that becomes part of a URL — a query parameter value, a path segment, a fragment — never a full URL at once. It escapes everything except letters, digits, and - _ . ! ~ * ' ( ), which means it also escapes /, ?, &, =, and # — exactly the characters that would otherwise be misread as URL structure instead of literal content.
encodeURI — for a whole URI you don't want to break
This escapes far less. It's built for a string that's already a complete URI, so it leaves structural characters like /, ?, &, =, and : untouched — encoding those would turn a working URL into a broken one. Run a single value through encodeURI instead of encodeURIComponent, and characters that needed escaping (&, =) sail through unescaped, silently corrupting whatever URL it gets embedded in.
escape() — don't
escape() predates both of the above and is deprecated (Annex B in the spec, kept only for legacy compatibility). Beyond that, it's genuinely broken for anything outside Latin-1: encoding an emoji produces a non-standard %uXXXX sequence that no URL parser, server, or decodeURIComponent understands — decodeURIComponent(escape("🙂")) throws URI malformed outright, rather than producing a wrong-but-harmless result.
URLSearchParams — encodes a slightly different set
Building a query string with URLSearchParams is usually the better move over hand-assembling one with encodeURIComponent calls and & joins — but it doesn't produce identical output. URLSearchParams follows the application/x-www-form-urlencoded convention: a space becomes + instead of %20, and it escapes ! ( ) ' ~ too, which encodeURIComponent leaves alone. Both are correct, valid encodings — they just don't match character-for-character, so don't assert string equality between the two in a test.
For the underlying rules both correct functions implement, see URL Encoding Explained. For how Node specifically diverges between its legacy querystring module and URLSearchParams, see URL Encoding in Node.js. The same component-vs-whole-URI split shows up differently in other languages — see URL Encoding in C# and URL Encoding in Go.
Valid examples
encodeURIComponent("hello world!") // "hello%20world!"A single value — space escaped to %20; ! is one of the few characters encodeURIComponent deliberately leaves alone.
encodeURI("https://example.com/search?q=hello world") // ".../search?q=hello%20world"A whole URI — only the space gets escaped; ?, =, and : are left alone since they're structural, not literal content.
new URLSearchParams({ tag: "js & css" }).toString() // "tag=js+%26+css"Building a query string the recommended way — URLSearchParams escapes & correctly and uses + for the space, per application/x-www-form-urlencoded.
Invalid examples
encodeURI("a&b=c") // "a&b=c" — unchangedencodeURI used on a single value instead of a whole URI — & and = are structural characters it deliberately leaves alone, so they pass through unescaped and can corrupt the query string this gets embedded in.
decodeURIComponent(escape("🙂")) // throws "URI malformed"escape() produces a non-standard %uXXXX sequence for anything outside Latin-1 — no real URL parser or decodeURIComponent understands it.
encodeURIComponent("hello world") === new URLSearchParams({ q: "hello world" }).toString()false — encodeURIComponent produces "hello%20world", URLSearchParams produces "q=hello+world". Both are valid encodings; they just don't match character-for-character.