URL Encoding Explained: Percent-Encoding and Special Characters
URLs can only safely contain a limited set of ASCII characters. When you need to include spaces, special characters, or Unicode in a URL, you need percent-encoding. This guide explains exactly how it works and when to use encodeURI vs encodeURIComponent.
Why URLs Need Encoding
The URL specification (RFC 3986) restricts URLs to a safe subset of ASCII characters. Characters outside this set — spaces, Unicode letters, many punctuation marks — would be ambiguous or break URL parsing. For example, a space in a URL looks identical to the end of the URL in many contexts, and & separates query parameters, so it can't appear unencoded as a parameter value.
Percent-encoding (also called URL encoding) is the mechanism for safely including any character in a URL by representing it as % followed by its two-digit hexadecimal ASCII code.
The Percent-Encoding Mechanism
Each unsafe character is replaced with %XX, where XX is the uppercase hexadecimal representation of the character's byte value in UTF-8:
| Character | Encoded | Reason |
|---|---|---|
| Space | %20 | Not allowed in URLs |
| # | %23 | Fragment delimiter |
| % | %25 | Encoding prefix itself |
| & | %26 | Query param separator |
| + | %2B | Means space in form encoding |
| = | %3D | Key-value separator |
| ? | %3F | Query string start |
| / | %2F | Path separator |
| é (U+00E9) | %C3%A9 | Non-ASCII (UTF-8: 2 bytes) |
| € (U+20AC) | %E2%82%AC | Non-ASCII (UTF-8: 3 bytes) |
Reserved vs Unreserved Characters
RFC 3986 defines two important categories:
Unreserved characters — safe anywhere in a URL, never need encoding:
A-Z a-z 0-9 - _ . ~Reserved characters — have special meaning in URL syntax. They must be percent-encoded when used as data (not as structural delimiters):
: / ? # [ ] @ ! $ & ' ( ) * + , ; =Everything else — spaces, non-ASCII Unicode, control characters — must always be encoded.
encodeURI vs encodeURIComponent
JavaScript provides two encoding functions, and choosing the wrong one is a common source of bugs:
encodeURI(url)
Encodes a complete URL. It leaves reserved characters unencoded because they are structural — ://, /, ?, &, =, and # all have specific meaning in a full URL.
encodeURI("https://example.com/search?q=hello world&lang=en")
// → "https://example.com/search?q=hello%20world&lang=en"
// Note: & and = are left intact (they're structural)encodeURIComponent(value)
Encodes a single value for use inside a URL component. It encodes reserved characters — including /, ?, #, &, and = — because those characters would break the URL structure if they appeared unencoded in a parameter value.
encodeURIComponent("price=10¤cy=USD/EUR")
// → "price%3D10%26currency%3DUSD%2FEUR"
// Note: = / & all encoded (they'd break query parsing unencoded)
// Building a URL safely:
const query = encodeURIComponent(userInput);
const url = `https://api.example.com/search?q=${query}`;Rule of thumb: use encodeURIComponent for individual values you're inserting into a URL. Use encodeURI only if you have a complete URL string and just need to make it safe without breaking its structure.
Query Strings and Form Data
HTML form submissions encode data using the application/x-www-form-urlencoded format, which has a quirk: spaces are encoded as + rather than %20. This applies only to query string values in form submissions — in URL paths, + is a literal plus sign.
When processing form data on the server, use a form-aware decoder (like PHP's $_GET, Python's urllib.parse.parse_qs, or Express's req.query) rather than a raw URL decoder, so + is correctly converted to a space.
Common Mistakes
Double encoding
Encoding an already-encoded string encodes the % sign itself: hello%20world becomes hello%2520world. Decode once to recover the original. Avoid double encoding by not encoding values that are already encoded.
Using encodeURI for parameter values
encodeURI does not encode & and =, so a parameter value containing those characters will corrupt the query string. Always use encodeURIComponent for individual values.
Forgetting that % must be encoded
If your data contains literal percent signs (e.g. "50% off"), they must be encoded as %25 before inserting into a URL. Both encodeURI and encodeURIComponent handle this automatically.
Encode and Decode URLs Online
Need to quickly encode a URL or decode a percent-encoded string? The URL Encoder on DevEssentials supports both encodeURIComponent and encodeURI modes — all client-side, nothing sent to any server.
Ready to encode or decode a URL? Open the URL Encoder →
Frequently Asked Questions
What is the difference between %20 and + for spaces in URLs?▾
%20 is the standard percent-encoding for a space character per RFC 3986. It is safe everywhere in a URL — in path segments, query strings, and fragments.
+ is part of the HTML form encoding format (application/x-www-form-urlencoded). When a browser submits a GET form, spaces in field values are encoded as +, not %20. A + only means 'space' in a query string context when decoded by a form parser. In a URL path segment, + is a literal plus sign.
Rule of thumb: use %20 for spaces in paths and URIs; be aware that + in query strings may mean space depending on how the server decodes it.
Which characters must be percent-encoded in a URL?▾
RFC 3986 defines two categories. Unreserved characters are safe anywhere and never need encoding: A–Z, a–z, 0–9, hyphen (-), underscore (_), period (.), and tilde (~).
Reserved characters have special meaning in URL syntax: : / ? # [ ] @ ! $ & ' ( ) * + , ; =. They must be percent-encoded when used as data (not as structural delimiters). Everything else — non-ASCII Unicode, spaces, control characters — must also be percent-encoded.
What is the difference between encodeURI and encodeURIComponent?▾
encodeURI(url) encodes a complete URL. It leaves reserved characters (: / ? # @ etc.) unencoded because they are part of the URL structure. Use this when you have a full URL string that you want to make safe without breaking its structure.
encodeURIComponent(value) encodes a single value intended for use inside a URL component (typically a query parameter value or path segment). It encodes reserved characters like /, ?, #, &, and = because those characters would break the URL structure if they appeared unencoded inside a parameter value. Use this for individual values you're building into a URL.
What is double encoding and how do I avoid it?▾
Double encoding happens when an already-encoded string gets encoded again. The percent sign % itself gets encoded to %25, so %20 (space) becomes %2520 (literal '%20' instead of a space).
This often happens when encoding a URL that already contains encoded values, or when a framework automatically encodes values that you've already encoded manually. To avoid it: encode values exactly once before building the URL, and don't encode a URL that has already been constructed.
How does URL encoding handle Unicode characters?▾
Unicode characters are first converted to their UTF-8 byte sequence, then each byte is percent-encoded. For example, the é character (U+00E9) is represented as two bytes in UTF-8: 0xC3 0xA9, which encodes to %C3%A9 in a URL.
This is why a single Unicode character can produce multiple percent-encoded sequences. The decodeURIComponent() function in JavaScript automatically converts these UTF-8 sequences back to Unicode characters.