URL Encoding Guide: Understanding Percent-Encoding for the Web
Complete guide to URL encoding. Learn encodeURIComponent vs encodeURI, handle special characters, avoid common mistakes, and work with URLs in any language.
Ever wondered why URLs sometimes contain strange sequences like %20 or %3D? That's URL encoding (also called percent-encoding), and understanding it is essential for working with web applications, APIs, and any system that uses URLs.
What is URL Encoding?
URL encoding converts characters into a format that can be safely transmitted in URLs. Since URLs have a restricted character set, special characters are replaced with a percent sign (%) followed by their two-digit hexadecimal value.
Space → %20
! → %21
# → %23
& → %26
= → %3D
? → %3F
Example:
Original: Hello World! How are you?
Encoded: Hello%20World%21%20How%20are%20you%3F
Why URL Encoding Exists
URLs were designed with a limited character set. Certain characters have special meanings in URLs:
https://example.com/path/to/page?name=John&age=30#section
│ │ │ │ │ │
│ │ │ │ │ └─ # starts fragment
│ │ │ │ └─ & separates parameters
│ │ │ └─ = separates key from value
│ │ └─ ? starts query string
│ └─ : separates scheme from host
└─ / separates path segments
If your data contains these characters, they must be encoded to avoid breaking the URL structure.
Without encoding:
/search?q=rock & roll ← Browser thinks "&" starts a new parameter
With encoding:
/search?q=rock%20%26%20roll ← Correctly sends "rock & roll" as the value
Character Categories
Unreserved Characters (Never Encoded)
These characters are safe to use anywhere in a URL:
- Letters: A-Z, a-z
- Digits: 0-9
- Special:
-_.~
Hello-World_2024.txt ← No encoding needed
Reserved Characters (Encoded When Used as Data)
These characters have special meanings in URLs. They must be encoded when used as data rather than delimiters:
| Character | Meaning | Encoded |
|---|---|---|
: | Scheme separator (http:) | %3A |
/ | Path separator | %2F |
? | Query string start | %3F |
# | Fragment start | %23 |
[ | IPv6 delimiter | %5B |
] | IPv6 delimiter | %5D |
@ | User info separator | %40 |
! | Sub-delimiter | %21 |
$ | Sub-delimiter | %24 |
& | Parameter separator | %26 |
' | Sub-delimiter | %27 |
( | Sub-delimiter | %28 |
) | Sub-delimiter | %29 |
* | Sub-delimiter | %2A |
+ | Space (in forms) | %2B |
, | Sub-delimiter | %2C |
; | Parameter separator | %3B |
= | Key-value separator | %3D |
Unsafe Characters (Always Encoded)
These characters are not allowed in URLs and must always be encoded:
| Character | Encoded | Notes |
|---|---|---|
| Space | %20 | Also + in query strings |
" | %22 | Double quote |
< | %3C | Less than |
> | %3E | Greater than |
\ | %5C | Backslash |
^ | %5E | Caret |
| ``` | %60 | Backtick |
{ | %7B | Left brace |
| ` | ` | %7C |
} | %7D | Right brace |
Non-ASCII Characters
Characters outside the ASCII range (like é, 中, 🎉) are encoded as their UTF-8 bytes:
café → caf%C3%A9
中文 → %E4%B8%AD%E6%96%87
🎉 → %F0%9F%8E%89
JavaScript Encoding Functions
encodeURIComponent()
Encodes a URI component (query parameter values, path segments). Encodes all characters except:
A-Z a-z 0-9 - _ . ! ~ * ' ( )
encodeURIComponent('hello world')
// "hello%20world"
encodeURIComponent('a=1&b=2')
// "a%3D1%26b%3D2"
encodeURIComponent('rock & roll')
// "rock%20%26%20roll"
encodeURIComponent('https://example.com')
// "https%3A%2F%2Fexample.com" ← Encodes : and /
Use when: Building query string values, path segments, or any part that should be treated as data.
encodeURI()
Encodes a complete URI. Preserves URL structure by NOT encoding:
; / ? : @ & = + $ , #
encodeURI('https://example.com/path?q=hello world')
// "https://example.com/path?q=hello%20world"
encodeURI('https://example.com/path?q=rock & roll')
// "https://example.com/path?q=rock%20&%20roll"
// Note: & is NOT encoded - this breaks the URL!
Use when: You have a complete URL that just needs non-ASCII or space characters encoded.
Warning: encodeURI() is rarely what you want. It doesn't encode & and =, which can break query strings.
decodeURIComponent() and decodeURI()
Reverse the encoding:
decodeURIComponent('hello%20world')
// "hello world"
decodeURIComponent('rock%20%26%20roll')
// "rock & roll"
decodeURI('https://example.com/path?q=hello%20world')
// "https://example.com/path?q=hello world"
URLSearchParams (Recommended)
The modern way to handle query strings - encoding is automatic:
// Building query strings
const params = new URLSearchParams();
params.set('name', 'John Doe');
params.set('query', 'rock & roll');
params.set('filter', 'price<100');
params.toString()
// "name=John+Doe&query=rock+%26+roll&filter=price%3C100"
// Parsing query strings
const parsed = new URLSearchParams('name=John+Doe&age=30');
parsed.get('name') // "John Doe"
parsed.get('age') // "30"
Note: URLSearchParams uses + for spaces (form encoding), not %20.
URL Constructor
For building complete URLs safely:
const url = new URL('https://example.com/search');
url.searchParams.set('q', 'hello world');
url.searchParams.set('filter', 'a=1&b=2');
url.toString()
// "https://example.com/search?q=hello+world&filter=a%3D1%26b%3D2"
url.href
// Same as toString()
Other Languages
Python
from urllib.parse import quote, unquote, urlencode
# Encode a string
quote('hello world') # 'hello%20world'
quote('rock & roll') # 'rock%20%26%20roll'
# Decode
unquote('hello%20world') # 'hello world'
# Build query string (recommended)
urlencode({'name': 'John Doe', 'query': 'rock & roll'})
# 'name=John+Doe&query=rock+%26+roll'
PHP
// Encode
urlencode('hello world'); // "hello+world"
rawurlencode('hello world'); // "hello%20world"
// Decode
urldecode('hello%20world'); // "hello world"
rawurldecode('hello+world'); // "hello+world" (doesn't decode +)
// Build query string
http_build_query(['name' => 'John Doe', 'query' => 'rock & roll']);
// "name=John+Doe&query=rock+%26+roll"
Java
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
// Encode
URLEncoder.encode("hello world", StandardCharsets.UTF_8);
// "hello+world"
// Decode
URLDecoder.decode("hello%20world", StandardCharsets.UTF_8);
// "hello world"
Go
import "net/url"
// Encode
url.QueryEscape("hello world") // "hello+world"
url.PathEscape("hello world") // "hello%20world"
// Decode
url.QueryUnescape("hello+world") // "hello world"
// Build URL with query
u, _ := url.Parse("https://example.com/search")
q := u.Query()
q.Set("name", "John Doe")
u.RawQuery = q.Encode()
// https://example.com/search?name=John+Doe
Space Encoding: %20 vs +
Two encodings exist for spaces:
| Encoding | Character | When Used |
|---|---|---|
| %20 | Space | URLs in general |
| + | Space | application/x-www-form-urlencoded (forms, query strings) |
// encodeURIComponent uses %20
encodeURIComponent('hello world') // "hello%20world"
// URLSearchParams uses +
new URLSearchParams({q: 'hello world'}).toString() // "q=hello+world"
Both are valid in query strings. Most servers accept either.
Common Mistakes
1. Double Encoding
Encoding an already-encoded string:
// First encoding
const encoded = encodeURIComponent('hello world');
// "hello%20world"
// Double encoding (WRONG!)
encodeURIComponent(encoded);
// "hello%2520world" ← %25 is the encoding of %!
// When decoded once, you get:
// "hello%20world" instead of "hello world"
Solution: Only encode raw data once, right before building the URL.
2. Using encodeURI() for Query Values
const search = 'rock & roll';
// WRONG: encodeURI doesn't encode &
const url = 'https://example.com/search?q=' + encodeURI(search);
// "https://example.com/search?q=rock%20&%20roll"
// Browser sees: q=rock AND roll= (broken!)
// RIGHT: encodeURIComponent encodes &
const url = 'https://example.com/search?q=' + encodeURIComponent(search);
// "https://example.com/search?q=rock%20%26%20roll"
3. Forgetting to Encode User Input
// User enters: ../../etc/passwd
const filename = userInput;
// WRONG: Path traversal attack!
fetch('/files/' + filename);
// RIGHT: Encode the path segment
fetch('/files/' + encodeURIComponent(filename));
4. Not Decoding When Needed
When you receive encoded data (e.g., from a query string), decode it before using:
// URL: /search?q=hello%20world
const urlParams = new URLSearchParams(window.location.search);
const query = urlParams.get('q'); // Automatically decoded: "hello world"
// If manually parsing:
const raw = 'hello%20world';
const decoded = decodeURIComponent(raw); // "hello world"
5. Encoding Entire URLs
const url = 'https://example.com/path?q=test';
// WRONG: Breaks the URL structure
encodeURIComponent(url);
// "https%3A%2F%2Fexample.com%2Fpath%3Fq%3Dtest"
// RIGHT: Only encode the parts that need it
const base = 'https://example.com/path';
const query = encodeURIComponent('hello world');
const fullUrl = base + '?q=' + query;
Security Considerations
Prevent Injection Attacks
Always encode user input before inserting into URLs:
// User input: javascript:alert('XSS')
const url = 'redirect?url=' + encodeURIComponent(userInput);
// Safe: "redirect?url=javascript%3Aalert%28%27XSS%27%29"
Validate After Decoding
Decode data before validation, not after:
// User submits: %2e%2e%2f (encoded ../)
const path = decodeURIComponent(userInput); // "../"
// Now validate the decoded value
if (path.includes('..')) {
throw new Error('Invalid path');
}
Complete Encoding Reference
| Character | URL Encoded | HTML Entity |
|---|---|---|
| (space) | %20 or + | |
| ! | %21 | - |
| " | %22 | " |
| # | %23 | - |
| $ | %24 | - |
| % | %25 | - |
| & | %26 | & |
| ' | %27 | - |
| ( | %28 | - |
| ) | %29 | - |
| * | %2A | - |
| + | %2B | - |
| , | %2C | - |
| / | %2F | - |
| : | %3A | - |
| ; | %3B | - |
| < | %3C | < |
| = | %3D | - |
| > | %3E | > |
| ? | %3F | - |
| @ | %40 | - |
| [ | %5B | - |
| \ | %5C | - |
| ] | %5D | - |
| ^ | %5E | - |
| ` | %60 | - |
| { | %7B | - |
| %7C | ||
| } | %7D | - |
| ~ | %7E | - |
Best Practices
- Use
URLSearchParamsfor query strings - it handles encoding automatically - Use
encodeURIComponent()for individual values (path segments, query values) - Never use
encodeURI()for query string values - Encode once, right before building the URL
- Decode data before validating it
- Use the URL constructor for building complete URLs safely
// Best practice: Use URL and URLSearchParams
const url = new URL('https://example.com/search');
url.searchParams.set('q', 'rock & roll');
url.searchParams.set('page', '1');
console.log(url.toString());
// "https://example.com/search?q=rock+%26+roll&page=1"
Use our URL Encoder/Decoder tool to quickly encode or decode URLs and see the transformations in real-time.
Related Tools
Related Articles
Try Our Free Tools
200+ browser-based tools for developers and creators. No uploads, complete privacy.
Explore All Tools