ReCaseText
8 min read

URL Encoding Explained: Percent-Encoding, Reserved Characters, and When to Encode

URLs can only contain a limited set of ASCII characters. Everything else must be percent-encoded. This guide explains RFC 3986, reserved vs. unreserved characters, and common encoding mistakes.

Every URL you see in a browser address bar follows a strict character set. Letters, digits, hyphens, periods, underscores, and tildes can appear as-is. Everything else — spaces, accented characters, ampersands, question marks used outside their special role, emoji, Chinese characters, and dozens of common punctuation marks — must be converted into a special format called percent-encoding before they can safely appear in a URL. If you've ever seen %20 in a URL and wondered what it means, that's percent-encoding: the space character (ASCII 32, hexadecimal 20) represented as a percent sign followed by its two-digit hex code.

This article explains why URL encoding exists, what the rules are, which characters need encoding in which contexts, and how to avoid the common mistakes that break links, corrupt query parameters, and cause subtle bugs in web applications.

Why URLs Need Encoding

URLs were designed in the early 1990s as part of the web's foundational standards. The original specification (RFC 1738, later superseded by RFC 3986) restricted URLs to a small subset of ASCII characters. This restriction exists for three reasons.

First, URLs must be unambiguous. Characters like ?, &, =, #, and / have structural meaning in a URL — they separate the query string, parameters, fragments, and path segments. If your data contains these characters (say, a search query that includes an ampersand), they must be encoded so the URL parser doesn't misinterpret them as structural delimiters.

Second, URLs must be transmittable across systems that may not support the full range of characters. Email headers, HTTP headers, HTML attributes, and many legacy protocols only reliably handle 7-bit ASCII. Percent-encoding converts any byte into three ASCII characters (%XX), ensuring the URL survives any transmission channel.

Third, URLs must be printable. Early web infrastructure included systems that couldn't handle control characters, binary data, or characters outside the ASCII range. Percent-encoding ensures every URL can be printed, copied, and pasted without corruption.

The Character Sets: Reserved, Unreserved, and Everything Else

RFC 3986 divides characters into three groups.

Unreserved characters can appear anywhere in a URL without encoding. These are: uppercase letters A–Z, lowercase letters a–z, digits 0–9, hyphen (-), period (.), underscore (_), and tilde (~). That's it — 66 characters total. These characters are guaranteed safe in any position in any URL component.

Reserved characters have special meaning in URL syntax. They are: :, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, =. When these characters are used for their reserved purpose (like / separating path segments or ? starting the query string), they must NOT be encoded. When they appear as data (like an ampersand in a company name within a query parameter value), they MUST be encoded.

All other characters — spaces, accented letters, CJK characters, emoji, control characters, and anything else not in the unreserved or reserved sets — must always be percent-encoded. The character is first encoded as UTF-8 bytes, then each byte is represented as %XX where XX is the uppercase hexadecimal value. For example: the space character is %20 (byte 0x20), the Euro sign is %E2%82%AC (three UTF-8 bytes), and the emoji 🔥 is %F0%9F%94%A5 (four UTF-8 bytes).

Our URL encoder handles all of this automatically — paste any text and it produces the correctly percent-encoded version. The URL decoder reverses the process.

The Space Character: %20 vs. +

The space character has two different encoded forms, and confusing them is one of the most common URL encoding mistakes.

In the path component of a URL (everything before the ?), a space must be encoded as %20. This is the standard percent-encoding defined by RFC 3986.

In the query string component (everything after the ?), a space can be encoded as either %20 or +. The + convention comes from the older application/x-www-form-urlencoded format used by HTML forms, defined in the HTML specification rather than the URI specification. When an HTML form with method="GET" is submitted, the browser encodes spaces in form field values as + rather than %20.

Most server-side frameworks handle both forms transparently in query strings. But using + in the path component is wrong — a server will interpret it as a literal plus sign, not a space. If you're constructing URLs programmatically, the safest approach is to always use %20 for spaces. Our URL encoder uses %20 by default.

Encoding in Different URL Components

The encoding rules differ slightly depending on where in the URL the character appears.

The path (/search/query%20here) allows unreserved characters plus :, @, !, $, &, ', (, ), *, +, ,, ;, = as literal sub-delimiters. Slashes separate path segments. Everything else must be percent-encoded.

The query string (?key=value&key2=value2) allows unreserved characters plus ?, /, :, @, !, $, ', (, ), *, +, ,, ; as literals. The & and = characters are used as key-value pair delimiters and assignment operators respectively. If your query parameter value contains & or = as data, they must be encoded as %26 and %3D.

The fragment (#section-name) has the same rules as the query string. The # character itself marks the beginning of the fragment and cannot appear literally within it (encode as %23).

The authority (the host and optional userinfo, like user:pass@example.com:8080) has its own sub-rules. Colons separate username from password and host from port. The @ separates userinfo from host. These must be encoded if they appear as data within these components.

This context-sensitivity is why generic "URL encode everything" functions sometimes produce incorrect results. JavaScript's encodeURI() encodes for a complete URI (leaving reserved characters intact), while encodeURIComponent() encodes for a URI component (encoding reserved characters too). Using encodeURI() on a query parameter value will leave & and = unencoded, breaking your query string. Using encodeURIComponent() on a complete URL will encode the :// and / characters, breaking the URL structure. You almost always want encodeURIComponent() for values and manual construction for the overall URL structure.

Common Encoding Mistakes

Double encoding happens when you encode a string that's already been encoded. The % in %20 gets encoded as %25, producing %2520. The URL looks like it contains the literal text %20 rather than a space. This is a frequent bug in web applications that pass URLs through multiple encoding layers — for example, a URL stored in a database is encoded when retrieved, then encoded again when inserted into an HTML attribute. The fix is to encode once, at the point of URL construction, and never re-encode.

Not encoding query parameter values is the most common security-relevant mistake. If a user-provided value is inserted into a URL without encoding, an attacker can inject additional parameters. For example, if a search query a&admin=true is inserted without encoding into /search?q=a&admin=true, the server sees two parameters: q=a and admin=true. This is a form of parameter injection. Always use encodeURIComponent() (or your language's equivalent) on every user-provided value before inserting it into a URL.

Encoding characters that don't need encoding is harmless but wasteful. Encoding a lowercase letter as %61 (the percent-encoding of a) is technically valid — the URL will work — but it's unnecessarily verbose and can cause issues with URL comparison (a server might treat /path and /%70ath as different URLs even though they resolve to the same resource).

Using the wrong function is rampant in JavaScript. The older escape() function is deprecated and doesn't handle Unicode correctly. encodeURI() is for complete URIs. encodeURIComponent() is for URI components (parameter values, path segments). In Node.js and modern browsers, the URL and URLSearchParams APIs handle encoding correctly and are the recommended approach.

URL Encoding and Internationalized Domain Names

Domain names (the host part of a URL) use a completely different encoding system called Punycode, defined in RFC 3492. Internationalized domain names like münchen.de or 例え.jp are converted to an ASCII-compatible encoding: xn--mnchen-3ya.de and xn--r8jz45g.jp. This conversion is handled by the browser and DNS system, not by percent-encoding. You can't percent-encode a domain name — it won't resolve.

The path and query components of a URL, however, do use percent-encoding for non-ASCII characters. This is why you sometimes see URLs like https://example.com/caf%C3%A9 — the path contains the UTF-8 percent-encoded form of "café." Modern browsers display these as their decoded form in the address bar (/café) for readability, but the underlying HTTP request uses the encoded form.

URL Encoding vs. HTML Encoding

URL encoding and HTML encoding solve different problems and must not be confused. URL encoding (percent-encoding) makes data safe for inclusion in URLs. HTML encoding (entity encoding) makes data safe for inclusion in HTML documents. They use different syntax: URL encoding uses %XX, while HTML encoding uses &name; or &#decimal; or &#xhex;.

If you need to put a URL inside an HTML attribute (like the href of an anchor tag), you need both: the URL's component values must be percent-encoded, and the resulting URL must be HTML-entity-encoded for the attribute context. For example, the URL /search?q=a%26b (searching for "a&b") must be written in HTML as /search?q=a%26b — the %26 is the URL encoding of &, and in this case the & in the HTML attribute doesn't need additional HTML encoding because there's no semicolon-delimited entity name following it. But if the URL contains &amp literally, it could be misinterpreted as the & entity.

This is why context-aware encoding libraries (like OWASP's Java Encoder or DOMPurify for JavaScript) are critical for web security. They understand which encoding to apply in which context. For more on HTML encoding specifically, see our article on HTML entity encoding and XSS prevention. You can also use our HTML encoder and URL encoder side by side to see the difference.

URL Encoding for SEO

Search engines handle URL encoding transparently — Google can index URLs with percent-encoded characters and displays them in decoded form in search results. However, clean, readable URLs are an SEO best practice. A URL like /blog/url-encoding-explained is better for click-through rates than /blog/url%20encoding%20explained (with encoded spaces) or /blog/url+encoding+explained (with plus signs).

For URL slugs specifically, the best practice is to convert spaces to hyphens (not encoded spaces), strip all characters that would need encoding, and lowercase everything. Our slug generator does exactly this — it takes any text and produces a clean, SEO-friendly, kebab-case slug with no percent-encoding needed. For more on slug conventions, see our article on naming conventions.

The Bottom Line

URL encoding is a solved problem with clear rules, but the context-sensitive nature of those rules — different components have different allowed characters, spaces have two encoded forms, and URL encoding must not be confused with HTML encoding — creates a surprising number of real-world bugs. The key principles are: encode every user-provided value with encodeURIComponent() (or your language's equivalent) before inserting it into a URL, never double-encode, use %20 for spaces (not +) in path components, and use a context-aware encoding library when constructing HTML that contains URLs. For quick encoding and decoding of individual values, our URL encoder and URL decoder handle it instantly.

References

RFC 3986 — Uniform Resource Identifier (URI): Generic Syntax — The authoritative specification for URL syntax and percent-encoding.

Wikipedia — Percent-encoding — Comprehensive overview of percent-encoding with character tables.

MDN — encodeURIComponent() — JavaScript's URI component encoding function, with examples.

WHATWG URL Standard — The living standard for URL parsing and serialization used by browsers.