URL Encoding Explained — Why %20 Means a Space
Why URLs can't contain arbitrary text, what percent-encoding really does, and the difference between encodeURI and encodeURIComponent.
You've probably seen %20 in a URL and wondered what it is. That's URL encoding doing its job — replacing a space with a percent code so URLs stay valid. The full system has more nuance than that, and getting it wrong causes some of the most common form bugs.
Encode or decode now: the URL Encoder / Decoder converts URLs in either direction, with component vs full-URI modes. Also parses query strings into a readable table.
Why URLs need encoding
URLs have structure: scheme://host/path?query#fragment. Several characters have special meaning:
?separates the path from the query string.#introduces the fragment (page anchor).&separates query parameters.=separates key from value./separates path segments.- Space,
",<,>aren't allowed at all.
When you want to include these characters as data in a URL (e.g. searching for "C++" or a user's email "ada@example.com"), you need to encode them so they don't get interpreted as structure.
How percent encoding works
Every character is represented by its UTF-8 byte values, each in hexadecimal, prefixed by %.
| Character | Encoded | Why |
|---|---|---|
| space | %20 | Spaces not allowed in URLs |
| & | %26 | Reserved (query separator) |
| = | %3D | Reserved (key=value) |
| ? | %3F | Reserved (query start) |
| # | %23 | Reserved (fragment) |
| + | %2B | Often means space in queries |
| % | %25 | Must itself be escaped |
| ñ | %C3%B1 | Non-ASCII (2 UTF-8 bytes) |
| ✨ | %E2%9C%A8 | Emoji (3 UTF-8 bytes) |
encodeURI vs encodeURIComponent
JavaScript provides two functions, and confusing them is the #1 URL bug:
encodeURI
Designed to encode a full URL. Preserves URL structure characters: :, /,?, #, =, &.
encodeURI("https://example.com/search?q=hello world")
// "https://example.com/search?q=hello%20world"Use when you have a complete URL and just want to escape unsafe characters.
encodeURIComponent
Designed to encode a single component (one query parameter value, one path segment). Escapes everything that isn't a basic letter/digit/mark.
encodeURIComponent("hello & welcome")
// "hello%20%26%20welcome"
encodeURIComponent("https://example.com")
// "https%3A%2F%2Fexample.com"Use when you're constructing URL parts piece by piece. The vast majority of the time, you want this one.
The rule of thumb
- Building a URL string by concatenating parts? Use encodeURIComponent on each part.
- Already have a complete URL and just want to fix unsafe characters? Use encodeURI.
- When in doubt, use encodeURIComponent. It's safer to over-encode than under-encode.
The query string format
Query strings have their own format: ?key=value&key2=value2. Some specifics:
- Spaces in query values are typically encoded as
+(not%20) in form-urlencoded format. URLSearchParams handles both. - Multiple values for one key can be repeated:
?tag=a&tag=b. Or sometimes comma-separated:?tags=a,b. Conventions vary. - Arrays and objects have no single standard — different frameworks encode them differently. Common patterns:
arr[]=1&arr[]=2(PHP),arr=1&arr=2(Express),arr=1,2(Stripe).
The URL Encoder parses query strings and shows decoded key/value pairs in a readable table.
The + vs %20 wrinkle
In query strings, + traditionally means space (legacy HTML form encoding). In path segments, + means a literal plus sign.
This causes bugs when you copy a URL from one place to another. Searching for "C++" in a form might send ?q=C%2B%2B (correct) or ?q=C++ (wrong on server side — interpreted as "C ").
Modern best practice: always use %20 for spaces and %2B for plus, even in query strings. URLSearchParams does this correctly.
UTF-8 in URLs
Modern URLs support Unicode via percent-encoded UTF-8 bytes. The character "café" (where é is one character) encodes as caf%C3%A9 (5 bytes after encoding).
Browsers display Unicode in the address bar but transmit the percent-encoded form. If you seecaf%C3%A9 in a log, that's "café" — perfectly normal.
Common bugs
- Encoding a full URL with encodeURIComponent.
https://example.combecomeshttps%3A%2F%2Fexample.com— broken if used as a link. - Double encoding. Encoding an already-encoded value.
%20becomes%2520. Symptom: spaces in URLs literally read "%20". - Forgetting to encode user input. User types & in a comment, comment URL breaks.
- Manually building query strings. Always use
URLSearchParamsin browsers,querystringmodule in Node.
JavaScript quick reference
// Build a URL safely
const url = new URL("https://example.com/search")
url.searchParams.set("q", "hello world")
url.searchParams.set("filter", "a & b")
console.log(url.toString())
// "https://example.com/search?q=hello+world&filter=a+%26+b"
// Parse a query string
const params = new URLSearchParams("?q=hello+world&page=2")
params.get("q") // "hello world"
params.get("page") // "2"
// Manual encoding (when not using URL object)
const encoded = encodeURIComponent("foo & bar")
const decoded = decodeURIComponent("foo%20%26%20bar")Server-side considerations
Different servers and frameworks decode URLs at different points. By the time your handler runs:
- Express has decoded query parameters via
req.query. - Path parameters via
req.paramsare decoded. - The raw URL is in
req.url(still encoded). - Headers like Authorization are NOT URL-decoded — they have their own format.
Test what you encode
Before shipping a feature that constructs URLs from user input, test with the obvious edge cases:
- Spaces in input.
- Special characters: &, =, ?, #, %, /, +.
- Non-ASCII: ñ, ✨, 中.
- Empty strings.
- Already-encoded strings (does your code double-encode?).
Quick conversions: URL Encoder / Decoder — encode user input, decode logs, parse query strings into tables. Pair with the Base64 when you need binary-safe encoding.