Every URL you construct in an application passes through encoding rules that most developers understand only partially. URL encoding — officially called percent-encoding — exists because URLs are restricted to the ASCII character set and a subset of ASCII characters have reserved meaning in URL syntax. Understanding when and how to encode URLs prevents security vulnerabilities, broken links, API integration failures, and frustrating edge cases.
What Is Percent-Encoding?
Percent-encoding converts characters to a safe format for use in URLs. A character is converted to its
UTF-8 byte representation, then each byte is written as %XX where XX is the two-digit
hexadecimal value of that byte.
Examples:
- Space (U+0020) →
%20 - Ampersand (&) →
%26 - Equals (=) →
%3D - Question mark (?) →
%3F - Forward slash (/) →
%2F
For non-ASCII characters, UTF-8 encoding is applied first. The emoji 😀 (U+1F600) encodes in UTF-8 as
F0 9F 98 80, which becomes %F0%9F%98%80 in percent-encoding.
Reserved vs Unreserved Characters
RFC 3986 defines which characters have structural meaning in URLs and which don't:
Unreserved Characters
Safe characters that never need encoding:
- Letters: A-Z, a-z
- Digits: 0-9
- Special:
- _ . ~
Reserved Characters
Characters with structural meaning in URLs:
| Character | Usage | Example |
|---|---|---|
| : | Scheme separator | https: |
| / | Path separator | /path/to/resource |
| ? | Query start | ?key=value |
| # | Fragment start | #section |
| & | Parameter separator | &key=value |
| = | Key-value separator | key=value |
Key rule: Reserved characters must be encoded when used as data (not structure). The reserved characters are structural — they define URL format. When you want to pass a reserved character as data, encode it.
encodeURI vs encodeURIComponent: The Critical Difference
JavaScript provides two encoding functions. They differ in which characters they preserve:
encodeURI()
Encodes a complete URL. Preserves reserved characters because they are structurally meaningful.
encodeURI("https://example.com/search?q=hello world")
// Result: "https://example.com/search?q=hello%20world"
// Note: : / ? are NOT encoded
encodeURIComponent()
Encodes a URL component (a value that will appear inside a URL). Encodes ALL special characters.
encodeURIComponent("hello world")
// Result: "hello%20world"
encodeURIComponent("user@example.com")
// Result: "user%40example.com" (@encoded)
When to Use Which?
WRONG — encodeURI for query parameters:
const search = "hello & goodbye";
const url = "https://example.com?q=" + encodeURI(search);
// Result: https://example.com?q=hello & goodbye
// BROKEN: & is preserved, interpreted as parameter separator!
CORRECT — encodeURIComponent for query values:
const search = "hello & goodbye";
const url = "https://example.com?q=" + encodeURIComponent(search);
// Result: https://example.com?q=hello%20%26%20goodbye
// CORRECT: &is encoded, treated as data
Building Query Strings Correctly
To construct a query string with multiple parameters, apply encodeURIComponent to both keys
and values:
const params = {
name: "Alice Smith",
email: "alice@example.com",
tags: "javascript, typescript"
};
const queryString = Object.entries(params)
.map(([key, value]) =>
encodeURIComponent(key) + "=" + encodeURIComponent(value)
)
.join("&");
const url = "https://api.example.com/users?" + queryString;
// Result: https://api.example.com/users?name=Alice%20Smith&email=alice%40example.com&tags=javascript%2C%20typescript
Or use the modern URLSearchParams API, which handles encoding automatically:
const params = new URLSearchParams({
name: "Alice Smith",
email: "alice@example.com"
});
const url = "https://api.example.com/users?" + params.toString();
// Result: name=Alice+Smith&email=alice%40example.com
// (URLSearchParams uses + for spaces in query strings)
Common Encoding Mistakes
Double-Encoding
Encoding an already-encoded URL encodes the % itself:
const encoded = "hello%20world";
encodeURIComponent(encoded);
// Result: "hello%2520world" (% becomes %25)
// WRONG: now decoding gives "hello%20world", not "hello world"
Solution: Check if input is already encoded before encoding.
Forgetting to Encode Spaces
Spaces must be encoded as %20 (or + in query strings per legacy convention,
though %20 is more reliable).
// WRONG
"https://example.com/search?q=hello world"
// CORRECT
"https://example.com/search?q=hello%20world"
Browser Auto-Encoding Illusion
Browsers auto-encode URLs you paste in the address bar, so manual URLs often "work" despite being technically invalid. This leads developers to overlook encoding. Always encode programmatically.
Decoding URLs
decodeURI() and decodeURIComponent() are the inverse functions. You need to
decode when:
- Reading query parameters: Extract values from
window.location.search - Processing webhook callbacks: URLs received from external services
- Logging or debugging: Convert encoded URLs to human-readable form
// Extract search param value
const params = new URLSearchParams(window.location.search);
const q = params.get("q"); // Automatically decoded
console.log(q); // "hello world" (not "hello%20world")
Tools That Help
For quick URL encoding/decoding and query string building, use the DevTools URL Encoder/Decoder. It handles:
- Encoding and decoding individual strings
- Parsing full URLs into components
- Query string builder for constructing parameters visually
- Support for UTF-8 and emoji
encodeURIComponent() for query parameter values. Use
encodeURI() only for complete URLs. When in doubt, encodeURIComponent() is the
safe choice for data values.