What is URL encoding?
URL encoding — formally called percent-encoding — is the mechanism that lets a URL carry characters that would otherwise be ambiguous or illegal. A URL is, at its core, just text built from a small, well-defined character set. When you need to include a character that isn't in that set, or a character that already has a special structural meaning, you replace it with a percent sign (%) followed by two hexadecimal digits representing that character's byte value in UTF-8.
For example, a space becomes %20, an ampersand becomes %26, and a forward slash becomes %2F. The two hex digits after the % spell out the byte: 0x20 is 32, the ASCII code for a space. Characters outside ASCII are first encoded to UTF-8, then each byte is percent-encoded — so an em dash (—) becomes %E2%80%94, three bytes, three escapes.
Reserved vs. unreserved characters
The URL specification (RFC 3986) splits characters into groups. The distinction is what drives every encoding decision.
Unreserved characters never need encoding. They are safe to appear literally anywhere:
- Letters
A-Zanda-z - Digits
0-9 - The four symbols
-_.~
Reserved characters have a structural job inside a URL — they act as delimiters that separate one part from another:
: / ? # [ ] @ ! $ & ' ( ) * + , ; =
A colon separates the scheme from the rest; a ? starts the query string; & separates query parameters; # starts a fragment, and so on. When one of these characters is meant as a delimiter, leave it alone. When it is meant as literal data inside a value, you must encode it — otherwise the parser will mistake your data for structure.
encodeURIComponent vs. encodeURI
This is the single most important distinction in practice, and choosing the wrong one is the most common URL bug. JavaScript ships two functions, and they differ entirely in scope.
encodeURIComponent — Component scope
Use this for a single piece of a URL: one query parameter value, or one path segment. It assumes the text is data, not structure, so it encodes the reserved delimiters too — including &, =, ?, /, and #. That is exactly what you want when a value might itself contain those characters.
const value = "a&b=c?d";
encodeURIComponent(value);
// "a%26b%3Dc%3Fd"
const url = "https://example.com/search?q=" + encodeURIComponent(value);
// the &, =, ? inside the value can't break the query string
encodeURI — Full URL scope
Use this when you have an entire, already-structured URL and only want to escape illegal characters (like spaces) without destroying the URL's anatomy. It deliberately leaves the reserved delimiters intact so that ://, ?, &, and # keep working.
const url = "https://example.com/my docs?tag=a&b";
encodeURI(url);
// "https://example.com/my%20docs?tag=a&b"
// the space is escaped; the ? & / : stay as delimiters
The rule of thumb: encoding a whole URL? use Full scope. Encoding one value to drop into a URL? use Component scope.
Common pitfalls
- Encoding the whole URL when you meant a component. If you run
encodeURIon a string that is really just a single value, embedded&and=characters survive and silently corrupt your query string. Use Component scope for values. - Double-encoding. Encoding text that is already encoded turns
%20into%2520, because the%itself becomes%25. The result decodes back to%20as literal text, not a space. Always encode raw input exactly once, and decode exactly once. - Spaces:
%20vs.+. In the path and most components, a space is%20. But inapplication/x-www-form-urlencodeddata — classic HTML form submissions and many query strings — a space is encoded as+, and a literal plus sign becomes%2B. Mixing the two conventions causes plus signs to appear as spaces or vice versa. Know which format the receiving system expects. - Forgetting non-ASCII is multi-byte. Accented letters and emoji expand into several
%HHpairs under UTF-8. That's correct, not a bug.
Worked examples
Input value: hello world & co
Component: hello%20world%20%26%20co
Full (encodeURI of that fragment): hello%20world%20&%20co
Input value: price=$5/kg
Component: price%3D%245%2Fkg
Input value: café
Component: caf%C3%A9 (é is two UTF-8 bytes)
Notice how Component scope escapes &, =, and / — protecting the value — while Full scope leaves them as delimiters.
Encode and decode with Formatly
Formatly's URL Encode / Decode tool lets you switch between Component and Full scope so you can match the exact behavior of encodeURIComponent or encodeURI, then decode back to verify your work. Paste your text, pick the scope, and read the result instantly. Everything runs 100% in your browser — nothing is uploaded to a server — so even URLs containing API keys, tokens, or personal data never leave your machine.