Unreserved Characters (Never Encoded)
These characters can appear in any part of a URL without encoding (RFC 3986 Β§2.3):
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9
- _ . ~
Everything else must be percent-encoded when used as data in URL components.
Reserved Characters
These have special meaning in URLs and must be encoded when used as data:
| Character | Encoded | Role in URL |
|---|
: | %3A | Scheme separator, port |
/ | %2F | Path separator |
? | %3F | Query string start |
# | %23 | Fragment start |
[ | %5B | IPv6 address |
] | %5D | IPv6 address |
@ | %40 | User info separator |
! | %21 | Sub-delimiter |
$ | %24 | Sub-delimiter |
& | %26 | Query parameter separator |
' | %27 | Sub-delimiter |
( | %28 | Sub-delimiter |
) | %29 | Sub-delimiter |
* | %2A | Sub-delimiter |
+ | %2B | Sub-delimiter / space in forms |
, | %2C | Sub-delimiter |
; | %3B | Sub-delimiter |
= | %3D | Key-value separator |
Common Percent Encodings
| Character | Encoded | Notes |
|---|
| (space) | %20 | Standard encoding |
| (space) | + | Form encoding only (application/x-www-form-urlencoded) |
% | %25 | The escape character itself |
" | %22 | Double quote |
< | %3C | Less than |
> | %3E | Greater than |
{ | %7B | Left brace |
} | %7D | Right brace |
| | %7C | Pipe |
\ | %5C | Backslash |
^ | %5E | Caret |
` | %60 | Backtick |
(newline) | %0A | Line feed (LF) |
(carriage return) | %0D | Carriage return (CR) |
(tab) | %09 | Horizontal tab |
encodeURI vs encodeURIComponent
| encodeURI() | encodeURIComponent() |
|---|
| Purpose | Encode a complete URL | Encode a single value |
| Use for | Full URLs with non-ASCII chars | Query param values, path segments |
| Encodes | Non-ASCII + some unsafe | Everything except A-Za-z0-9 - _ . ~ |
| Preserves | : / ? # [ ] @ ! $ & ' ( ) * + , ; = | Nothing (all reserved chars encoded) |
| Space | %20 | %20 |
Rule: Use encodeURIComponent() for values. Use encodeURI() only for complete URLs.
// CORRECT β encoding a query parameter value
const value = 'price > 100 & category = shoes';
const url = `https://example.com/search?q=${encodeURIComponent(value)}`;
// https://example.com/search?q=price%20%3E%20100%20%26%20category%20%3D%20shoes
// WRONG β encodeURIComponent on a full URL breaks it
encodeURIComponent('https://example.com/path?q=hello')
// https%3A%2F%2Fexample.com%2Fpath%3Fq%3Dhello β broken!
Encoding by Language
| Language | Encode (RFC 3986) | Encode (form) | Decode |
|---|
| JavaScript | encodeURIComponent(s) | new URLSearchParams({k: s}) | decodeURIComponent(s) |
| Python | urllib.parse.quote(s) | urllib.parse.urlencode({k: s}) | urllib.parse.unquote(s) |
| PHP | rawurlencode($s) | urlencode($s) | rawurldecode($s) |
| Go | url.PathEscape(s) | url.QueryEscape(s) | url.PathUnescape(s) |
| Java | URI(β¦).toASCIIString() | URLEncoder.encode(s, UTF_8) | URLDecoder.decode(s, UTF_8) |
| Ruby | ERB::Util.url_encode(s) | URI.encode_www_form_component(s) | URI.decode_www_form_component(s) |
| C# | Uri.EscapeDataString(s) | HttpUtility.UrlEncode(s) | Uri.UnescapeDataString(s) |
| Bash | jq -sRr @uri | curl --data-urlencode | python3 -c "β¦unquote()" |
UTF-8 Multi-Byte Encoding
Non-ASCII characters are first converted to UTF-8 bytes, then each byte is percent-encoded:
| Character | UTF-8 Bytes | Encoded |
|---|
Γ© (U+00E9) | C3 A9 | %C3%A9 |
Γ± (U+00F1) | C3 B1 | %C3%B1 |
Π (U+0414) | D0 94 | %D0%94 |
ζ₯ (U+65E5) | E6 97 A5 | %E6%97%A5 |
β¬ (U+20AC) | E2 82 AC | %E2%82%AC |
π (U+1F680) | F0 9F 9A 80 | %F0%9F%9A%80 |
Space Encoding: %20 vs +
| Format | Space becomes | Standard | Used by |
|---|
| Percent-encoding | %20 | RFC 3986 | Path segments, general URLs |
| Form encoding | + | WHATWG URL / HTML forms | Query strings from form submissions |
%20 is universally safe. + only works in query strings and may not be decoded correctly in path segments.
Common Mistakes
| Mistake | Problem | Fix |
|---|
| Double encoding | %20 β %2520 | Always encode raw values, never pre-encoded |
encodeURIComponent on full URL | Breaks ://, /, ? | Use encodeURI() or URL API for full URLs |
encodeURI on a parameter value | & and = not encoded | Use encodeURIComponent() for values |
Forgetting to encode % | 100% β ambiguous | 100%25 (encode the percent sign) |
+ in path segments | Not decoded as space | Use %2B for literal +, %20 for space |
| Not decoding server-side | %C3%A9 shown as text | Decode before processing |