URL encoding in JavaScript
JavaScript gives you four built-in functions for URL encoding — and they have different rules. Picking the wrong one is the source of most URL-encoding bugs in front-end and Node.js code. This article maps each function to the situations it’s designed for, with the gotchas spelled out.
The four built-ins
JavaScript’s URL-related global functions:
encodeURIComponent(text)— encodes everything exceptA-Z a-z 0-9 - _ . ! ~ * ' ( )encodeURI(text)— encodes only characters that aren’t allowed anywhere in a URLescape(text)— obsolete, don’t use; produces non-standard%uXXXXsequences- The
URLconstructor andURLSearchParams— modern, recommended for parsing and building
encodeURIComponent: the safe default
Use encodeURIComponent for individual values — query parameter values, path segments, hash fragments. It encodes everything that has any chance of being structurally meaningful.
encodeURIComponent('Hello, World!')
// → 'Hello%2C%20World!'
encodeURIComponent('https://example.com/path')
// → 'https%3A%2F%2Fexample.com%2Fpath'
Notice the second example encodes the colons and slashes — that’s what you want when embedding one URL inside another (e.g., as a redirect parameter).
encodeURI: only when you have a structural URL
Use encodeURI only when you have a complete URL where structural characters like :, /, ?, & are meant to stay literal. It doesn’t encode the reserved characters.
encodeURI('https://example.com/search?q=hello world')
// → 'https://example.com/search?q=hello%20world'
The space gets encoded, but the colon and slashes stay. The problem: in real code, the part of the URL that has data is rarely identical to the part you want to encode. If the user’s search query is “A/B testing,” encodeURI leaves the slash literal — which becomes a path separator and breaks your URL.
In practice, encodeURI is the wrong choice almost every time. Reach for encodeURIComponent by default; use encodeURI only when you understand exactly why you need to preserve reserved characters.
escape: never use
The global escape() function is from JavaScript’s prehistory. It uses non-standard %uXXXX for non-ASCII characters. The result isn’t a valid URL-encoded string — it’s a JavaScript-specific format that no other system understands.
escape('café')
// → 'caf%E9' // wrong — should be UTF-8: '%C3%A9'
escape('☺')
// → '%u263A' // not valid URL encoding
Both escape and unescape are marked deprecated in the JavaScript spec. The MDN docs explicitly recommend not using them.
URLSearchParams: the right way to build query strings
When you’re constructing a URL with parameters, don’t concatenate strings — use URLSearchParams. It handles all the encoding for you, correctly.
const params = new URLSearchParams();
params.append('q', 'Hello, World!');
params.append('lang', 'en');
params.append('tags', 'js');
params.append('tags', 'url');
params.toString();
// → 'q=Hello%2C+World%21&lang=en&tags=js&tags=url'
Notice how the comma and exclamation in the value got encoded, the space became + (form-encoded variant), and repeated keys (tags) were appended correctly. This is what an HTML form would produce.
To parse a query string the same way:
const params = new URLSearchParams(window.location.search);
params.get('q'); // 'Hello, World!' (auto-decoded)
params.getAll('tags'); // ['js', 'url']
URL: the modern URL parser
For working with whole URLs, the URL constructor is the right tool. It splits a URL into structured properties and handles all the encoding rules per WHATWG URL spec.
const u = new URL('https://example.com/path?q=hello#section');
u.protocol; // 'https:'
u.host; // 'example.com'
u.pathname; // '/path'
u.search; // '?q=hello'
u.hash; // '#section'
u.searchParams; // URLSearchParams object
You can also build URLs by mutating the properties:
const u = new URL('https://example.com');
u.pathname = '/products/My Item'; // space gets encoded
u.searchParams.set('lang', 'fr');
u.toString();
// → 'https://example.com/products/My%20Item?lang=fr'
Common pitfalls
Pitfall 1: Concatenating instead of using URLSearchParams
// Wrong — you have to remember to encode manually
const url = 'https://api.example.com/search?q=' + userInput;
// Right
const u = new URL('https://api.example.com/search');
u.searchParams.set('q', userInput);
const url = u.toString();
Pitfall 2: Double-encoding
// User input is already encoded once
const userInput = 'Hello%2C%20World';
// Wrong: encoding again
encodeURIComponent(userInput);
// → 'Hello%252C%2520World' — the % got encoded as %25
If you receive input you suspect might already be encoded, decode once before re-encoding.
Pitfall 3: Using + for spaces in path components
URLSearchParams.toString() uses + for spaces because it produces form-encoded output — correct for query strings. But if you’re building a path component, you need %20:
// Wrong — the + means literal + in a path
path: '/users/John+Doe/profile'
// Right
path: '/users/' + encodeURIComponent('John Doe') + '/profile'
// → '/users/John%20Doe/profile'
Pitfall 4: Encoding the entire URL
// Wrong — this produces a broken URL
const fullUrl = encodeURIComponent('https://example.com/search?q=hello');
// → 'https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello'
// You’ve encoded the URL’s own structure, which means it’s no longer a URL
// Right — only encode parts that are data
const baseUrl = 'https://example.com/search';
const q = encodeURIComponent('hello, world');
const fullUrl = baseUrl + '?q=' + q;
Picking the right function
| Situation | Use |
|---|---|
| Building a query string | URLSearchParams |
| Parsing a URL | new URL() |
| Encoding a single value (query param, path segment, hash) | encodeURIComponent |
| Decoding a single value | decodeURIComponent |
| Encoding a whole URL with reserved chars intact | encodeURI (rarely) |
Default to URLSearchParams and new URL() when you can; they encode correctly automatically. Reach for encodeURIComponent when you have a value you specifically need to encode and the surrounding context doesn’t have its own helper. Avoid escape and encodeURI unless you have a specific reason.
Found this useful? Try the URL decoder or browse all tools.