URL Encoding Explained: What Are Percent-Encoded Characters?
Percent-encoding converts characters that cannot appear literally in a URL into %XX form. How it works, and when to use encodeURIComponent over encodeURI.
ProfitTool Team
Security & Dev
What URL Encoding Is
URLs may only contain a restricted set of ASCII characters. Anything outside that set — a space, an accented letter, an ampersand inside a value — has to be represented as one or more percent-encoded bytes: a % followed by two hexadecimal digits.
https://example.com/search?q=café latte&sort=price
https://example.com/search?q=caf%C3%A9%20latte&sort=priceThe rules come from RFC 3986. Percent-encoding is often called URL encoding; they are the same thing.
Which Characters Are Safe
RFC 3986 divides characters into three groups, and the group determines whether encoding is required, forbidden, or a judgement call.
| Group | Characters | Encoding |
|---|---|---|
| Unreserved | A–Z a–z 0–9 - . _ ~ | Never encode — encoded and unencoded forms are equivalent |
| Reserved | : / ? # [ ] @ ! $ & ' ( ) * + , ; = | Encode when the character is data; leave it when it is structure |
| Everything else | Spaces, <, >, ", {, }, \, ^, backtick, all non-ASCII | Always encode |
The reserved group is where the thinking happens. / separates path segments, ? starts the query, & separates parameters, = joins a key to its value. Those same characters appearing inside a value must be encoded, or the parser will read them as structure.
# Wrong — the & inside the value ends the parameter early
?company=Smith & Jones&page=2
# Right
?company=Smith%20%26%20Jones&page=2The Encoding Algorithm
For any character that needs encoding:
- Encode the character as UTF-8, producing one or more bytes.
- Write each byte as
%plus two uppercase hex digits.
ASCII characters are one byte and produce one escape. Non-ASCII characters produce several:
| Character | UTF-8 bytes | Encoded |
|---|---|---|
| space | 20 | %20 |
& | 26 | %26 |
# | 23 | %23 |
é | C3 A9 | %C3%A9 |
€ | E2 82 AC | %E2%82%AC |
| 😀 | F0 9F 98 80 | %F0%9F%98%80 |
RFC 3986 specifies uppercase hex digits, though decoders accept either. Non-ASCII characters are always UTF-8 in modern URLs — this is what makes %C3%A9 and not some other byte sequence the correct encoding of é.
The Space Problem: %20 vs +
Spaces have two encodings, and both are correct in the right place.
| Encoding | Defined by | Where it applies |
|---|---|---|
%20 | RFC 3986 | Anywhere in a URL — path, query, fragment |
+ | HTML application/x-www-form-urlencoded | Query strings and form bodies only |
The + form comes from HTML form submission, not from the URI specification. In a query string, most servers accept both. In a path segment, + is a literal plus sign and will not be decoded as a space.
/search?q=hello+world → q = "hello world" (form decoding)
/search?q=hello%20world → q = "hello world" (always)
/files/hello+world.txt → filename contains a literal +
/files/hello%20world.txt → filename contains a space%20 is the safe default everywhere. Reach for + only when you are deliberately producing form-encoded data.
Encoding in Practice
JavaScript
Two functions, and choosing the wrong one is the most common URL bug in the language.
// encodeURI — for a COMPLETE URL. Leaves reserved characters alone
// so the URL keeps working.
encodeURI("https://example.com/my page?q=a&b=1");
// → "https://example.com/my%20page?q=a&b=1"
// encodeURIComponent — for a SINGLE VALUE. Escapes reserved
// characters so they cannot be mistaken for structure.
encodeURIComponent("Smith & Jones");
// → "Smith%20%26%20Jones"Using encodeURI on a value leaves & and = intact, which lets a user-supplied value inject extra query parameters. Using encodeURIComponent on a whole URL escapes the :// and / separators and destroys it.
The rule: encodeURIComponent for each piece, encodeURI for the whole thing — and in practice, build URLs with URLSearchParams instead.
const url = new URL("https://example.com/search");
url.searchParams.set("q", "Smith & Jones");
url.searchParams.set("tag", "café");
url.toString();
// → "https://example.com/search?q=Smith+%26+Jones&tag=caf%C3%A9"URLSearchParams encodes every value correctly and cannot be given the wrong function. Note that it uses + for spaces, since it produces form-encoded query strings — correct here, and correctly decoded by URLSearchParams on the other side.
One historical wrinkle: encodeURIComponent does not escape !, ', (, ) or *, which RFC 3986 lists as reserved sub-delimiters. It is almost never a problem, but strict parsers occasionally care:
const strict = (s) =>
encodeURIComponent(s).replace(/[!'()*]/g, (c) =>
"%" + c.charCodeAt(0).toString(16).toUpperCase());Python
from urllib.parse import quote, quote_plus, urlencode, unquote
quote("Smith & Jones") # 'Smith%20%26%20Jones'
quote("a/b") # 'a/b' — '/' is safe by default
quote("a/b", safe="") # 'a%2Fb' — encode it as data
quote_plus("Smith & Jones") # 'Smith+%26+Jones' — form style
urlencode({"q": "Smith & Jones", "tag": "café"})
# 'q=Smith+%26+Jones&tag=caf%C3%A9'
unquote("caf%C3%A9") # 'café'quote treats / as safe because its usual job is encoding a path. When encoding a value that might contain a slash, pass safe="".
Java
// Form encoding — space becomes '+', NOT '%20'
URLEncoder.encode("Smith & Jones", StandardCharsets.UTF_8);
// → "Smith+%26+Jones"Despite the name, URLEncoder implements application/x-www-form-urlencoded. It is correct for query values and wrong for path segments. For a full URI, use URI or a builder from your HTTP client.
Command Line
# Encode
python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "Smith & Jones"
# Decode
python3 -c 'import sys,urllib.parse; print(urllib.parse.unquote(sys.argv[1]))' "caf%C3%A9"
# curl encodes a query parameter for you
curl -G https://example.com/search --data-urlencode "q=Smith & Jones"Double Encoding
Because % is itself a special character, it encodes to %25. Encoding an already-encoded string therefore produces a broken URL:
Original: Smith & Jones
Encoded once: Smith%20%26%20Jones
Encoded twice: Smith%2520%2526%2520Jones
Decoded once: Smith%20%26%20Jones ← the visible symptomThe tell-tale sign is %25 in a URL, or literal %20 sequences appearing in rendered output. It happens when a value is encoded in one layer and encoded again by another — a client that escapes before handing a value to a library that escapes too.
The fix is structural rather than a matter of decoding twice: encode exactly once, at the boundary where the URL is constructed. Never store encoded values; store raw values and encode at the point of use.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
%20 appears literally on the page | Value decoded once too few times, or double-encoded | Trace where encoding is applied; encode once |
%25 in the URL | Double encoding | Remove the redundant encoding layer |
Query parameter truncated at & | Unencoded & inside a value | Encode the value with encodeURIComponent / quote(safe="") |
Accented characters become é | Bytes decoded as Latin-1 instead of UTF-8 | Decode as UTF-8; é is the classic mis-decode of é |
Space becomes + where it should stay a space | Form encoding applied to a path segment | Use %20 in paths |
Everything after # missing on the server | Fragments are never sent in the request | Move the value into the query string |
%2F in a path rejected or normalised | Many servers refuse encoded slashes in paths by default | Restructure the path, or enable it explicitly (AllowEncodedSlashes in Apache) |
+ in an email address becomes a space | Form decoding applied to user+tag@example.com | Encode as %2B |
URL Encoding Is Not Punycode
A related but separate mechanism handles non-ASCII domain names. café.com is converted to xn--caf-dma.com using Punycode (RFC 3492) as part of Internationalized Domain Names — not percent-encoding.
The split is worth remembering: the host is Punycode, everything after it is percent-encoded. Percent-encoding a hostname does not work.
Frequently Asked Questions
Is URL encoding a security measure? No. It is a data-representation mechanism. It does not sanitise input or prevent injection — encoding a value correctly stops it corrupting the URL's structure, which is a correctness property, not a defence against a malicious payload.
Should I encode the whole URL or just parts? Just the parts. Encode each path segment and each query value individually, then assemble. Encoding an assembled URL escapes its own separators.
Are %2F and / interchangeable? Not in a path. / is a segment separator; %2F is a literal slash inside one segment. Many servers reject or silently normalise %2F in paths, so avoid relying on it.
Does URL encoding affect SEO? Mildly. Consistency matters more than the choice itself: pick one canonical form and make sure internal links, canonical tags and sitemaps all agree, so encoded and unencoded variants do not become duplicate URLs.
Why do some sites show readable non-ASCII characters in the address bar? Browsers decode percent-encoded UTF-8 for display while sending the encoded form over the wire. The URL is still encoded in the request.
Encode and decode URLs, switch between encodeURI and encodeURIComponent modes, and inspect malformed sequences with the URL Encoder / Decoder. To break a URL into protocol, host, path and query parameters, use the URL Inspector & Parser — and for the Base64URL variant used inside JWTs, see the Base64 & Base64URL Encoder / Decoder.
ProfitTool Team
Security & DevWritten and maintained by the ProfitTool team. Where an article references a ProfitTool utility, the behaviour described is the behaviour of the tool on this site — you can open it and check the output yourself.
Try the Interactive Tools Mentioned
Free to use with no ProfitTool signup. Data handling varies by tool.
More from the Knowledge Hub
Why URLs Contain Percent Signs: Percent-Encoding Explained
%20, %2F and %40 are not errors — they are characters encoded so they survive a URL. What the common codes mean, and when a literal % must be escaped too.
Developer ToolsHow to Decode a JWT Token (Without a Library)
You can decode a JWT token in any language using nothing but base64url decoding and JSON parsing. Here is how to do it in JavaScript, Python and the browser.
Developer ToolsWhat Is a JWT? JSON Web Tokens Explained
A plain-language guide to JSON Web Tokens — what they contain, how they are structured, and where they are used in modern authentication systems.