Percent-encoding hides in three places: a whole URL, a single value, and a query string built from
several of them. This page reads all three back into text — pasted by hand, byte for byte — and
splits a query into its key/value pairs so you don't have to trace the &s yourself.
Reading one escape by hand
A percent triplet is one byte written in hex: %20 is the byte 0x20, which is a space. %2B is
0x2B, a literal plus. Multi-byte characters take more than one triplet — %E1%BA%BF is three
bytes, 0xE1 0xBA 0xBF, which together form one UTF-8 character, ế. Put a run of these next to
plain ASCII and Ti%E1%BA%BFng%20Vi%E1%BB%87t decodes to Tiếng Việt: the ASCII letters pass
through untouched, the accented ones each cost three triplets, and the one %20 becomes the space
between the two words.
Choosing Component, Full URI, or Form mode
The three modes decode a different amount of the input, and picking the wrong one either leaves
escapes untouched or corrupts structure that was never meant to move. Component unescapes every
percent-encoded character it recognizes, which is right for a single value pulled out on its own —
a query parameter, a path segment, a cookie. Full URI is for a whole address pasted as one
piece: it deliberately leaves the escaped forms of / ? # & = + $ , : ; @ alone, so a slash that
was legitimately percent-encoded inside a filename (%2F) doesn't unescape into a literal / and
get misread as a new path segment. Form behaves like Component but also reads a + as a space,
matching how a browser encodes an HTML form submission — use it for anything that came out of an
application/x-www-form-urlencoded body or a query string, since a literal + has no other way to
survive that round trip.
Two ways a decode can fail
100% fails at the very last character — nothing follows the %, so there's no second hex digit
to read. 50%2 off fails one character earlier, at the %, because %2 is followed by a space
rather than a hex digit. Both are reported as an invalid percent-encoded sequence, at the exact
index of the offending %.
A different kind of failure happens when every escape is syntactically fine but the bytes they spell
don't add up to valid text. %E1 alone decodes as a well-formed single-byte escape, but 0xE1 is
the first byte of a three-byte UTF-8 character with no continuation bytes following it — there is no
text there to produce. %FF%FE is the same problem with two bytes that never start valid UTF-8 at
all. Both report as not valid UTF-8, a message that means "these bytes were never text," not
"you made a typo."
Pulling a query string apart
Paste https://example.com/search?q=hello+world&lang=vi#frag and the table below shows two rows:
q → hello world, lang → vi. The path before the ? and the #frag after it are both set
aside — neither is part of the query — and the + in hello+world decodes to a space, because a
query string follows form rules.
A query string also has more than one valid spelling: a space can be written as + or %20, and a
literal . decodes the same whether or not it was ever escaped as %2E — so writing one back out
from its decoded pieces can look different from what you pasted, character for character, while
meaning exactly the same thing.