Percent-encoding, briefly
URLs may only contain a limited set of ASCII characters (RFC 3986). Everything else, and any reserved character that would otherwise be read as a delimiter, is written as % followed by two hex digits for each byte of its UTF-8 encoding. A space is %20, & is %26, and é is %C3%A9.
Component vs full URL
The two modes mirror the two JavaScript functions, and choosing the wrong one is the most common URL-encoding bug.
Component (encodeURIComponent) | Full URL (encodeURI) | |
|---|---|---|
| Use for | A single value: a query parameter, a path segment, a form field | A complete URL that already has its structure |
| Leaves alone | A–Z a–z 0–9 - _ . ! ~ * ' ( ) | Those plus ; , / ? : @ & = + $ # |
a b&c=d/é | a%20b%26c%3Dd%2F%C3%A9 | a%20b&c=d/%C3%A9 |
If you encode a value with full-URL mode, an & inside it will split your query string. If you encode a whole URL with component mode, the :// and ? are destroyed. Encode values individually, then assemble the URL.
Spaces: %20 or +
application/x-www-form-urlencoded, the format browsers use for HTML form submissions and most query strings, encodes a space as +. RFC 3986 percent-encoding uses %20. Both are decoded correctly by most servers in the query string, but only %20 is valid in a path. The checkbox switches between the two conventions in both directions; when decoding, tick it if the input came from a form or a query string.
Decoding errors
A % that is not followed by two hexadecimal digits, or a sequence of bytes that is not valid UTF-8, is a malformed escape. Browsers throw URIError: malformed URI sequence. The tool reports the position so you can see whether the string was truncated (a common cause when copying from logs) or double-encoded (%2520 is an encoded %20; decode twice).
In code
JavaScript
encodeURIComponent("café au lait"); // "caf%C3%A9%20au%20lait"
new URLSearchParams({ q: "café au lait" }).toString(); // "q=caf%C3%A9+au+lait"
const u = new URL("https://example.com/search?q=a%20b");
u.searchParams.get("q"); // "a b"
Python
from urllib.parse import quote, unquote, urlencode, urlparse, parse_qs
quote("café au lait") # 'caf%C3%A9%20au%20lait'
quote("a/b", safe="") # 'a%2Fb' (quote keeps '/' by default)
urlencode({"q": "café au lait"}) # 'q=caf%C3%A9+au+lait'
parse_qs(urlparse(url).query)
URL parser
The parser above uses the browser's URL class, which implements the WHATWG URL Standard: the same rules the address bar applies. It normalises the input (lower-cases the host, removes default ports, resolves . and .. in the path, percent-encodes what needs it) and lists each query parameter decoded. Relative URLs are rejected because there is no base to resolve them against.