Percent-Encoding Reference: Reserved & Unreserved Characters

May 27, 20266 min read

What Is Percent-Encoding?

Percent-encoding is the mechanism defined in RFC 3986 for representing data within a URI. It replaces characters with a percent sign % followed by two uppercase hexadecimal digits representing the character's byte value.

Space   → %20
&       → %26
é (UTF-8) → %C3%A9

This reference covers every character you need to know about when building or parsing URLs.

Reserved Characters

Reserved characters have special meaning in URI syntax. When they appear as data rather than delimiters, they must be percent-encoded.

Complete Reserved Characters Table

CharHexEncodedRole in URI Syntax
:3A%3AScheme separator (https:), port separator (:443)
/2F%2FPath separator (/api/users)
?3F%3FQuery string start (?q=search)
#23%23Fragment identifier (#section)
[5B%5BIPv6 address delimiter
]5D%5DIPv6 address delimiter
@40%40Authority separator (user@host)
!21%21Sub-delim
$24%24Sub-delim
&26%26Query parameter separator (a=1&b=2)
'27%27Sub-delim
(28%28Sub-delim
)29%29Sub-delim
*2A%2ASub-delim
+2B%2BSub-delim (also means space in form encoding)
,2C%2CSub-delim
;3B%3BSub-delim (param separator)
=3D%3DQuery parameter assignment (key=value)

When to encode reserved characters

  • As data in query strings: ?url=https%3A%2F%2Fexample.com
  • As data in path segments: /search/hello%20world
  • When the character's delimiter role is not intended: ?q=AT%26T (encoding the & in "AT&T")

When NOT to encode reserved characters

  • / between path segments: /api/users/42 — leave as-is
  • ? starting a query string: ?search=hello — leave as-is
  • = assigning query values: ?page=1 — leave as-is
  • & separating parameters: ?a=1&b=2 — leave as-is

Unreserved Characters

Unreserved characters never need encoding and should not be encoded. Encoding them produces equivalent but unnecessarily long URLs.

CategoryCharactersCount
Uppercase lettersA B C ... Z26
Lowercase lettersa b c ... z26
Digits0 1 2 ... 910
Hyphen-1
Period.1
Underscore_1
Tilde~1

Total: 66 unreserved characters

Why tilde (~) matters

Older specifications (RFC 2396) classified ~ as a reserved character. RFC 3986 moved it to unreserved. Some legacy systems still encode ~ as %7E, but this is unnecessary and should be avoided.

Common Encoded Values Quick Reference

The table below covers frequently encoded characters with practical context:

CharacterEncodedCommon Scenario
Space%20Spaces in any URL component
"%22Quotes in HTML attribute URLs
%%25Literal percent sign (avoid double-encoding)
<%3CAngle brackets in URL data
>%3EAngle brackets in URL data
\%5CBackslash in Windows paths
^%5ECaret in data values
`%60Backtick in data values
{%7BCurly brace in JSON data
``%7C
}%7DCurly brace in JSON data
Tab%09Tab character in form data
Newline%0ALine break in text

ASCII Printable Characters Full Table

Every printable ASCII character and its encoding status:

DecCharEncodedStatusDecCharEncodedStatus
32Space%20Encode58:%3AReserved
33!%21Reserved59;%3BReserved
34"%22Encode60<%3CEncode
35#%23Reserved61=%3DReserved
36$%24Reserved62>%3EEncode
37%%25Encode63?%3FReserved
38&%26Reserved64@%40Reserved
39'%27Reserved91[%5BReserved
40(%28Reserved92\%5CEncode
41)%29Reserved93]%5DReserved
42*%2AReserved94^%5EEncode
43+%2BReserved95_Unreserved
44,%2CReserved96`%60Encode
45-Unreserved123{%7BEncode
46.Unreserved124|%7CEncode
47/%2FReserved125}%7DEncode
48–570–9Unreserved126~Unreserved

Characters 65–90 (A–Z) and 97–122 (a–z) are all unreserved and omitted from this table for brevity.

Unicode and UTF-8 Encoding

Non-ASCII characters use UTF-8 encoding before percent-encoding. Each UTF-8 byte becomes a separate %XX sequence.

CharacterUTF-8 BytesPercent-Encoded
éC3 A9%C3%A9
©C2 A9%C2%A9
E2 82 AC%E2%82%AC
E4 B8 AD%E4%B8%AD
🎉F0 9F 8E 89%F0%9F%8E%89

Practical example

// Encoding a URL with Unicode
const name = 'José García';
const encoded = encodeURIComponent(name);
// "Jos%C3%A9%20Garc%C3%ADa"

// The space could also be + in form encoding
const formEncoded = new URLSearchParams({ name }).toString();
// "name=Jos%C3%A9+Garc%C3%ADa"

4 Practical Examples

1. Passing a URL as a query parameter

const targetUrl = 'https://example.com/search?q=hello&lang=en';
const link = `/redirect?url=${encodeURIComponent(targetUrl)}`;
// "/redirect?url=https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello%26lang%3Den"

2. Encoding JSON data in a URL

const filters = JSON.stringify({ category: 'A&B', price: '<100' });
const url = `/products?f=${encodeURIComponent(filters)}`;
// "/products?f=%7B%22category%22%3A%22A%26B%22%2C%22price%22%3A%22%3C100%22%7D"
const subject = 'Questions & Answers';
const body = 'What is 50% off?";
const mailto = `mailto:help@example.com?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;

4. Avoiding double-encoding

// WRONG: Double-encoding produces %2520 instead of %20
const already = encodeURIComponent('hello world'); // "hello%20world"
const double = encodeURIComponent(already);         // "hello%2520world"

// CORRECT: Check before encoding
function safeEncode(str) {
  try {
    return encodeURIComponent(decodeURIComponent(str));
  } catch {
    return encodeURIComponent(str);
  }
}

Try It Yourself

Encode and decode any string instantly with our free URL Encoder/Decoder tool. Perfect for debugging URLs, testing API parameters, or learning percent-encoding hands-on.