Developer FIELD NOTES · 4 MIN READ
URL Encoding: When %20 Matters
How percent-encoding works: which characters may ride bare, why spaces become %20 or +, how %XX hides UTF-8 bytes, the double-encoding trap, and how to parse a URL into parts.
A URL is an address with grammar: certain characters are punctuation that separates parts, and everything that is not punctuation must travel in a safe disguise. URL encoding — percent-encoding — is that disguise, and the difference between knowing and guessing it is the difference between a working query string and one that silently truncates at the first &.
What may ride bare, and what must be dressed
The grammar divides characters into two camps. Unreserved characters can appear anywhere literally: letters, digits, and - _ . ~ (encoders also leave ! ' ( ) * alone). Everything else is either reserved — punctuation with a job, like ? & = # / : — or simply unsafe to transmit, like a space. Reserved characters are the tricky half: a & in a query value is legal text but fatal grammar, because it terminates the value and starts a new parameter. So when "rock & roll" travels as a search term, it must become rock%20%26%20roll — the space and the ampersand both dressed. The same string sent bare would parse as two parameters, rock and roll, which is exactly the class of bug nobody notices in testing.
%20 versus +
Two encodings exist for a space, and they belong to different layers. %20 is the universal percent-encoding, correct everywhere in a URL. + means "space" only inside query strings submitted by HTML forms (the application/x-www-form-urlencoded convention), which is why a search box can produce ?q=rock+%26+roll and browsers will decode it back to "rock & roll". The trap is scope: a + in a path is just a plus, and some servers treat a + in a query as a literal plus. When in doubt, %20 is never wrong — it decodes identically everywhere and cannot be misread as addition.
Percent-encoding hides bytes, not characters
The %XX sequences are hexadecimal bytes, which matters the moment text leaves plain ASCII. The é in "café" is not one byte: in UTF-8 it is two bytes, C3 A9, so the encoded form is caf%C3%A9 — and "café menu" becomes caf%C3%A9%20menu. Multi-byte scripts grow accordingly, which is why a Japanese query can balloon to three times its visible length. Decoding reverses this exactly: decodeURIComponent reads the bytes and reconstructs the characters, so caf%C3%A9%20menu comes back as "café menu". The byte-level story of why é is two bytes lives in How Text Encoding Works.
The double-encoding trap
Encode a string that is already encoded and you get %2520: the % itself becomes %25, and the "20" rides along as text. Decode that once and you see %20 — looking broken but actually half-decoded. The bug usually enters through a component that encodes defensively (a framework, a redirect, a template helper) and meets a developer who encoded manually "just in case". The hygiene rules: encode exactly once, at the boundary where the value enters the URL; decode exactly once, where it leaves; and if you must handle text of unknown history, decode repeatedly until nothing percent-shaped remains, then encode once. A batch decoder makes auditing a list of copied links — campaign URLs, exported logs — a one-glance job.
Parsing: the parts a URL is made of
Encoding exists to protect the boundary lines of a URL's grammar, so it helps to see the grammar. Take https://ultraquick.tools/tools/url-encoder?ref=learn#how: the scheme is https:, the host is ultraquick.tools, the path is /tools/url-encoder, the query is ?ref=learn, and the fragment is #how. A parser returns each part separately — and crucially decodes the query parameters one by one, so ?q=rock%26roll&page=2 yields q = "rock&roll" and page = "2", not one mangled string. Parsers also handle the everyday conveniences: a missing scheme is usually assumed to be https, and the fragment never reaches the server, since the browser consumes it locally. This is also why reserved characters are reserved — each one marks a boundary between these parts, as How URL Slugs Work shows from the path side.
Try it yourself
Type "rock & roll" or "café menu" into the URL Encoder & Decoder and watch which characters get dressed and which ride bare, then decode the results back. Paste a handful of encoded links — campaign URLs, log excerpts, %2520 casualties — into the URL Decode Batch to decode them all at once, and dissect any full URL into scheme, host, path, query, and fragment with the URL Parser to see the grammar the encoding protects.