A Developer's Guide to URL Encoding and Percent-Encoding
A Developer's Guide to URL Encoding and Percent-Encoding
If you have ever seen %20 appear where a space should be in a URL, or noticed %2F replacing a forward slash, you have encountered URL encoding — also called percent-encoding. It is one of the most fundamental concepts in web development, yet it is frequently misunderstood and misused, leading to broken links, failed API requests, and confusing bugs.
This guide explains exactly what URL encoding is, which characters require encoding and why, the critical differences between JavaScript's encoding functions, and how to handle edge cases like Unicode characters, form data, and query strings.
Why URL Encoding Exists
The URL specification (RFC 3986) defines a limited set of characters that are allowed in a URL without modification — letters A–Z, a–z, digits 0–9, and a handful of special characters (-, _, ., ~). Every other character must be percent-encoded before it can appear in a URL.
The reason is that URLs are transmitted as ASCII text through protocols and infrastructure that were designed before Unicode existed. Characters outside the safe set would be misinterpreted by servers, proxies, and browsers. The forward slash / already means "path separator." The ? separates the path from the query string. The & separates query parameters. If your data contains these characters, they must be encoded so the URL structure remains unambiguous.
Example:
A search for C++ programming & algorithms needs to become a URL-safe query string:
?q=C%2B%2B+programming+%26+algorithms
Or using %20 for spaces instead of +:
?q=C%2B%2B%20programming%20%26%20algorithms
Without encoding, the & would be interpreted as a query parameter separator, breaking the intended meaning.
How Percent-Encoding Works
Percent-encoding converts each byte of the character (in UTF-8) to a two-digit hexadecimal value prefixed with %.
| Character | UTF-8 bytes | Percent-encoded |
|---|---|---|
| Space | 0x20 | %20 |
+ | 0x2B | %2B |
/ | 0x2F | %2F |
@ | 0x40 | %40 |
# | 0x23 | %23 |
& | 0x26 | %26 |
= | 0x3D | %3D |
? | 0x3F | %3F |
For multi-byte Unicode characters (emoji, Arabic, Chinese, etc.), each byte is encoded separately:
The Arabic word مرحبا ("hello") in UTF-8 is six bytes, encoded as:
%D9%85%D8%B1%D8%AD%D8%A8%D8%A7
Reserved vs. Unreserved Characters
The URL specification divides characters into three categories:
Unreserved characters — safe in any part of a URL, never need encoding:
A-Z a-z 0-9 - _ . ~
Reserved characters — have special meaning in URL structure. Must be encoded when used as data, but not when used as delimiters:
: / ? # [ ] @ ! $ & ' ( ) * + , ; =
All other characters — must always be encoded.
encodeURI vs. encodeURIComponent
JavaScript provides two built-in encoding functions, and choosing the wrong one is a very common source of bugs.
encodeURI()
Encodes a complete URL. It does NOT encode reserved characters because they are needed as structural delimiters:
encodeURI('https://example.com/search?q=hello world&lang=en')
// Output: 'https://example.com/search?q=hello%20world&lang=en'
The ://, /, ?, and & are preserved because they structure the URL.
Use this: When you have a full URL that you need to make safe for transmission.
encodeURIComponent()
Encodes a URL component (a single value, not the full URL). It DOES encode reserved characters:
encodeURIComponent('hello world & more')
// Output: 'hello%20world%20%26%20more'
encodeURIComponent('C++')
// Output: 'C%2B%2B'
Use this: When encoding individual query parameter values, form field values, or path segments that you are dynamically inserting into a URL.
The Most Common Mistake
Building query strings manually without encodeURIComponent:
// WRONG — if name contains &, ?, =, or spaces, the URL breaks
const url = `https://api.example.com/users?name=${name}&city=${city}`;
// CORRECT
const url = `https://api.example.com/users?name=${encodeURIComponent(name)}&city=${encodeURIComponent(city)}`;
Or better yet, use URLSearchParams which handles encoding automatically:
const params = new URLSearchParams({ name, city });
const url = `https://api.example.com/users?${params}`;
Spaces: %20 vs. +
In query strings, spaces are sometimes encoded as + instead of %20. This is a convention from HTML form encoding (the application/x-www-form-urlencoded content type) where the browser encodes form submissions with + for spaces.
Most web servers and frameworks accept both + and %20 for spaces in query strings, but %20 is the technically correct representation per RFC 3986. Outside of query strings (in path segments, for example), only %20 is valid — + in a URL path means a literal plus sign.
Best practice: Use %20 (via encodeURIComponent) unless you are specifically implementing HTML form encoding.
Decoding URL-Encoded Strings
Decoding reverses the process:
decodeURIComponent('hello%20world%20%26%20more')
// Output: 'hello world & more'
decodeURI('https://example.com/search?q=hello%20world')
// Output: 'https://example.com/search?q=hello world'
Our URL Encoder/Decoder tool handles both encoding and decoding instantly, including multi-byte Unicode characters, with no data leaving your browser.
Double-Encoding: A Frequent Bug
Double-encoding happens when encoded text gets encoded again:
hello world → hello%20world → hello%2520world
%25 is the encoding for the % character itself, so %2520 decodes to %20, not a space. This causes "URL not found" errors or garbled query parameters.
Common cause: Calling encodeURIComponent twice, or encoding a URL that already contains encoded characters. Always work with decoded (raw) values before encoding, and encode exactly once before including in a URL.
Unicode and International Characters
Modern web applications support international users whose names, addresses, and search queries contain characters outside ASCII. URL encoding handles all Unicode correctly via UTF-8:
encodeURIComponent('日本語') // Japanese: "Japanese language"
// Output: '%E6%97%A5%E6%9C%AC%E8%AA%9E'
encodeURIComponent('مرحبا') // Arabic: "Hello"
// Output: '%D9%85%D8%B1%D8%AD%D8%A8%D8%A7'
Modern browsers display internationalized domain names (IDN) and encoded paths in their decoded form in the address bar for readability, but the actual HTTP request uses the encoded form.
Summary
URL encoding ensures that any data can be safely transmitted as part of a URL without ambiguity or corruption.
Key takeaways:
- Only letters, digits,
-,_,.,~are safe in a URL without encoding - Use
encodeURIComponent()for individual values (query parameters, path segments) - Use
encodeURI()only for full URLs - Prefer
URLSearchParamsto avoid manual encoding mistakes - Spaces are
%20in paths;+is acceptable only in query strings - Avoid double-encoding — encode raw values exactly once
- Encode and decode instantly with the URL Encoder/Decoder tool