URLs can only contain a specific set of ASCII characters. Everything else — spaces, accented letters, symbols like & or =, non-Latin characters, emoji — must be converted into a safe format before being placed in a URL. This conversion is called URL encoding or percent encoding. Getting it wrong breaks links, causes 400 API errors, and creates security vulnerabilities.
How Percent Encoding Works
A character that cannot appear in a URL is replaced by a percent sign followed by two hexadecimal digits representing the character's UTF-8 byte value:
| Character | Encoded form | Notes |
|---|---|---|
| Space | %20 | Also + in form data |
& | %26 | Separates query params if unencoded |
= | %3D | Separates key=value if unencoded |
+ | %2B | Unencoded + means space in form data |
# | %23 | Fragment separator if unencoded |
/ | %2F | Path separator if unencoded |
£ | %C2%A3 | Multi-byte UTF-8 character |
| 😀 | %F0%9F%98%80 | 4-byte UTF-8 emoji |
Safe Characters — No Encoding Needed
The RFC 3986 specification defines unreserved characters that are always safe in URLs without encoding:
A–Z a–z 0–9 - _ . ~
Everything else must be encoded when it appears as data (not as part of the URL's structure).
encodeURI vs encodeURIComponent — The Critical Distinction
JavaScript provides two built-in URL encoding functions. Confusing them is one of the most common URL-related bugs:
// encodeURI — encodes a complete URL
// Preserves: : / ? # [ ] @ ! $ & ' ( ) * + , ; =
encodeURI('https://example.com/search?q=hello world&lang=en')
// → 'https://example.com/search?q=hello%20world&lang=en'
// encodeURIComponent — encodes a URL component (a value)
// Encodes ALL special characters including : / ? # & =
encodeURIComponent('hello world&lang=en')
// → 'hello%20world%26lang%3Den'
// ✅ Correct: build URL safely
const query = 'price < £100';
const url = `https://example.com/search?q=${encodeURIComponent(query)}`;
// → 'https://example.com/search?q=price%20%3C%20%C2%A3100'
Rule: Use encodeURIComponent() whenever you are inserting a dynamic value into a URL (query string parameter, path segment, fragment). Use encodeURI() only when encoding an already-assembled URL that just needs unsafe characters cleaned up.
URLSearchParams — The Modern Alternative
In modern JavaScript, URLSearchParams handles query string encoding automatically and is often cleaner than manual encoding:
const params = new URLSearchParams({
q: 'price < £100',
category: 'finance & tax',
page: '1'
});
const url = `https://example.com/search?${params.toString()}`;
// → 'https://example.com/search?q=price+%3C+%C2%A3100&category=finance+%26+tax&page=1'
Note that URLSearchParams encodes spaces as + (form-encoding style) rather than %20 — both are valid for query strings.
Common URL Encoding Mistakes
- Using encodeURI instead of encodeURIComponent on values. If a query string value contains
&or=,encodeURIwill not encode them, breaking the URL structure. - Double encoding. Encoding an already-encoded URL encodes the
%signs:%20becomes%2520. Always decode before encoding again. - Not encoding path segments. If a file name or slug contains spaces or special characters, the path must be encoded too — not just the query string.
- Forgetting to decode in server-side code. URL parameters arrive at the server encoded. Most frameworks decode them automatically, but manual string handling in middleware may miss this.
- Manually building URLs with string concatenation. Always use
URL,URLSearchParams, or a URL-building library instead of string concatenation — they handle encoding for you.
Decoding URLs
decodeURI('https://example.com/search?q=hello%20world')
// → 'https://example.com/search?q=hello world'
decodeURIComponent('hello%20world%26lang%3Den')
// → 'hello world&lang=en'