URL Encoding Explained: Percent-Encoding and When to Use It
URL encoding (percent-encoding) ensures special characters are safely transmitted in URLs and query strings. Learn which characters need encoding, how the algorithm works, and the difference between encodeURI and encodeURIComponent.
Why URLs Need Encoding
A URL can only contain a limited set of characters defined in RFC 3986. Characters outside this set — spaces, non-ASCII letters, certain punctuation — must be encoded before they appear in a URL.
If you try to include a space in a URL without encoding it, browsers typically replace it with + or %20, but servers and libraries interpret these differently. Being explicit with encoding prevents subtle bugs, broken links, and security issues.
How Percent-Encoding Works
Percent-encoding replaces each unsafe byte with a percent sign followed by two hexadecimal digits representing that byte's value:
Space (0x20) → %20
é (U+00E9, UTF-8: 0xC3 0xA9) → %C3%A9
# (0x23) → %23
For multi-byte Unicode characters, the character is first converted to UTF-8, then each byte is percent-encoded individually.
Safe vs Unsafe Characters
Always safe (no encoding needed): Letters A–Z, a–z, digits 0–9, and the four unreserved symbols: -, _, ., ~
Reserved characters have special meaning in URLs and should be encoded when used as data:
| Character | Encoded | URL meaning |
|---|---|---|
# | %23 | Fragment identifier |
? | %3F | Query string start |
& | %26 | Query parameter separator |
= | %3D | Parameter key/value separator |
/ | %2F | Path separator |
+ | %2B | Treated as space in form data (application/x-www-form-urlencoded) |
@ | %40 | Userinfo delimiter |
JavaScript: encodeURI vs encodeURIComponent
JavaScript has two built-in encoding functions, and choosing the wrong one is a common mistake.
encodeURI(url)
Encodes a complete URL. It does not encode characters that are valid URL structure characters (/, ?, #, &, =, :):
encodeURI("https://example.com/search?q=hello world&lang=en");
// "https://example.com/search?q=hello%20world&lang=en"
encodeURIComponent(value)
Encodes a URL component — a single parameter value or path segment. It encodes reserved characters too:
encodeURIComponent("hello world & goodbye");
// "hello%20world%20%26%20goodbye"
// Correct way to build a query string:
const q = encodeURIComponent(userInput);
const url = `https://example.com/search?q=${q}`;
Rule of thumb: Use encodeURIComponent for individual query parameter keys and values. Use encodeURI only for full URLs where you want to preserve the URL structure.
The URLSearchParams API
In modern JavaScript, the cleanest way to build query strings is with URLSearchParams. It handles encoding automatically:
const params = new URLSearchParams({
q: "hello world",
lang: "en",
tags: "c++ & python",
});
console.log(params.toString());
// "q=hello+world&lang=en&tags=c%2B%2B+%26+python"
Note that URLSearchParams uses + for spaces (form encoding), while encodeURIComponent uses %20. Both are valid in query strings, but choose one and be consistent.
Decoding
Decoding is the reverse operation:
decodeURIComponent("hello%20world%20%26%20goodbye");
// "hello world & goodbye"
Always decode values received from URL parameters before using them — but do so after parsing, not before. Decoding before parsing can turn an encoded %2F into /, which would corrupt the URL structure.
Security Considerations
Never construct URLs by concatenating unencoded user input. This can allow path traversal attacks or open redirects:
// UNSAFE — user input can inject arbitrary path segments
const url = `/user/${userId}/profile`;
// SAFE
const url = `/user/${encodeURIComponent(userId)}/profile`;
In server-side code, use a trusted URL-building library rather than string concatenation. In browser JavaScript, use URL and URLSearchParams.
Double-Encoding
A common bug is double-encoding values — encoding an already-encoded string. This turns %20 into %2520 (because % itself gets encoded to %25). Always track whether a value has already been encoded, and decode before re-encoding if needed.