URL Encoding Explained: What %20 Means and When to Use encodeURIComponent
Learn what URL encoding (percent encoding) is, why %20 represents a space, the difference between encodeURIComponent and encodeURI, and how to avoid common encoding bugs.
Every time you see %20 in a URL, you're looking at percent encoding — the mechanism that makes URLs safe for special characters. It is simple once you understand it, but confusing the two JavaScript encoding functions (encodeURIComponent vs encodeURI) causes bugs that can be surprisingly hard to track down.
Why URLs Need Encoding
A URL can only contain a limited set of characters defined in RFC 3986: letters (A–Z, a–z), digits (0–9), and a small set of reserved and unreserved symbols (-._~:/?#[]@!$&'()*+,;=). Everything else must be encoded.
The problem is that some of those reserved characters — like ?, &, =, and # — have structural meaning in a URL. If a query parameter value contains an &, it looks like the start of a new parameter. If it contains a #, it looks like a fragment identifier. URL encoding escapes these characters so they can appear safely in values without being interpreted as URL structure.
How Percent Encoding Works
Each character to be encoded is converted to its byte value in UTF-8, and each byte is written as % followed by two hexadecimal digits.
Common encodings:
| Character | Percent encoded | |-----------|----------------| | Space | %20 | | & | %26 | | = | %3D | | + | %2B | | ? | %3F | | / | %2F | | # | %23 | | @ | %40 | | é (UTF-8: 0xC3 0xA9) | %C3%A9 | | 中 (UTF-8: 0xE4 0xB8 0xAD) | %E4%B8%AD |
Multi-byte UTF-8 characters produce multiple %XX pairs — one per byte.
encodeURIComponent vs encodeURI — The Critical Difference
JavaScript provides two built-in functions and choosing the wrong one is one of the most common URL bugs:
encodeURIComponent
Encodes everything except unreserved characters: A–Z a–z 0–9 - _ . ! ~ * ' ( )
It encodes structural URL characters: /, ?, #, &, =, :, @, +
Use for: individual query parameter keys and values.
const query = encodeURIComponent('price>=100&category=books')
// "price%3E%3D100%26category%3Dbooks"
const url = https://example.com/search?q=${encodeURIComponent('hello world & more')} // "https://example.com/search?q=hello%20world%20%26%20more"
encodeURI
Encodes everything that encodeURIComponent encodes, but preserves structural URL characters: / : ? # [ ] @ ! $ & ' ( ) * + , ; =
Use for: encoding a complete URL while keeping its structure navigable.
const rawUrl = 'https://example.com/search?q=hello world'
const safeUrl = encodeURI(rawUrl)
// "https://example.com/search?q=hello%20world"
// Note: ? and = are preserved — the URL structure is intact
The classic mistake: using encodeURI on a query parameter value that contains & or =:
// ❌ Bug: encodeURI leaves & unencoded
const value = 'a=1&b=2'
const url = https://api.example.com/data?filter=${encodeURI(value)}
// "https://api.example.com/data?filter=a=1&b=2"
// The server sees TWO parameters: filter=a=1 and b=2
// ✓ Correct: encodeURIComponent encodes & and = const url2 = https://api.example.com/data?filter=${encodeURIComponent(value)} // "https://api.example.com/data?filter=a%3D1%26b%3D2"
%20 vs + — Two Ways to Encode a Space
There are two conventions for encoding spaces in URLs, and mixing them causes subtle bugs:
%20— the RFC 3986 standard. Safe everywhere in a URL (path, query, fragment).+— theapplication/x-www-form-urlencodedformat used by HTML forms. Only valid as a space in query strings. In a URL path,+is a literal plus sign, not a space.
encodeURIComponent produces %20. HTML form submissions produce +. Servers typically accept both in query strings, but %20 is unambiguous.
// encodeURIComponent → %20
encodeURIComponent('hello world') // "hello%20world"
// URLSearchParams → + (form encoding) new URLSearchParams({ q: 'hello world' }).toString() // "q=hello+world"
If you're building a URL manually, use encodeURIComponent. If you're building a query string from an object, prefer URLSearchParams.
Building URLs Correctly
The cleanest way to construct URLs in JavaScript is with the URL API:
const url = new URL('https://example.com/search')
url.searchParams.set('q', 'hello world & more')
url.searchParams.set('page', '1')
console.log(url.toString())
// "https://example.com/search?q=hello+world+%26+more&page=1"
// (URLSearchParams uses + for spaces — which is fine for query strings)
Or encode manually with encodeURIComponent:
function buildUrl(base, params) {
const query = Object.entries(params)
.map(([k, v]) => ${encodeURIComponent(k)}=${encodeURIComponent(v)})
.join('&')
return ${base}?${query}
}
buildUrl('https://api.example.com/search', { q: 'café & restaurants', location: 'New York, NY', }) // "https://api.example.com/search?q=caf%C3%A9%20%26%20restaurants&location=New%20York%2C%20NY"
Decoding URLs
To decode a percent-encoded string back to plain text:
decodeURIComponent('hello%20world%20%26%20more')
// "hello world & more"
// Safe decode (won't throw on malformed sequences) function safeDecode(str) { try { return decodeURIComponent(str.replace(/\+/g, '%20')) } catch { return str } }
The .replace(/\+/g, '%20') handles form-encoded query strings where spaces are +.
Common Bugs Summary
| Bug | Cause | Fix | |-----|-------|-----| | & in value breaks query string | Used encodeURI instead of encodeURIComponent | Use encodeURIComponent for parameter values | | %2F in path causes 404 | Encoded slashes in a path that should be literal | Don't encode path separators; encode path segments individually | | + decoded as + not space | Forgot to replace + before decodeURIComponent | str.replace(/\+/g, '%20') before decoding | | Non-ASCII characters corrupt the URL | Using deprecated escape() instead of encodeURIComponent | Always use encodeURIComponent, which is UTF-8 aware |
Try it now: Use our free URL Encoder / Decoder to percent-encode any text or URL and decode any encoded string — with a visual breakdown of URL components and diff highlighting of encoded characters.
Read next: What Is Base64 Encoding? — the other common encoding scheme you'll encounter in web development, used for binary data in JWT tokens, data URIs, and API keys.