URL decoding reverses URL encoding: it walks through a percent-encoded string and replaces each %XX triplet with the byte whose hex value is XX, leaving unencoded characters untouched. The tool above decodes input in your browser using decodeURIComponent; nothing leaves your machine.
What the decoder actually does
Input: hello%20world%21%20%E2%9C%93
h e l l o [%20] w o r l d [%21] [%20] [%E2%9C%93]
Step 1 — replace %XX triplets with raw bytes:
68 65 6c 6c 6f 20 77 6f 72 6c 64 21 20 e2 9c 93
Step 2 — interpret bytes as UTF-8:
h e l l o w o r l d ! ✓
(single-byte ASCII) (3-byte ✓)
Output: hello world! ✓
Decoding is two distinct phases: hex-pair substitution, then UTF-8 interpretation. Most decoders fold them together, but the two-phase mental model explains the failure modes below.
The + problem
In path segments, + decodes to literal +. In query strings under the historic application/x-www-form-urlencoded convention, + decodes to a space. Decoders behave differently:
decodeURIComponent(JavaScript) —+decodes to literal+always.- HTML form parsers —
+decodes to space in query strings. - Most server-side query parsers —
+decodes to space in the query, literal+in the path.
If a query parameter looks like name=John+Smith and your decoder leaves it as John+Smith, you may have used the wrong decoder for the context. The tool above mirrors decodeURIComponent — apply a + → space replacement before decoding if you’re decoding form-encoded query input.
Errors a decoder can throw
decodeURIComponent throws URIError: URI malformed in two cases:
- A
%not followed by two hex digits.hello%worldis invalid — the decoder expected hex after%. Strip or escape stray%characters before decoding. - Byte sequences that aren’t valid UTF-8. A percent-encoded sequence that decodes to bytes that don’t form a legal UTF-8 sequence (e.g.,
%C3followed by an ASCII letter, where the high-bit byte should be followed by another high-bit byte) raises an error.
The tool above shows the browser’s error (“URI malformed”) but not the position, so search the input for a % that isn’t followed by two hex digits.
Decoding non-UTF-8 input
Some legacy systems encode URLs in ISO-8859-1 (Latin-1) rather than UTF-8. The character é in Latin-1 is the single byte 0xE9, which percent-encodes to %E9. A UTF-8-strict decoder rejects %E9 because 0xE9 alone isn’t a valid UTF-8 byte sequence — it’s the start byte of a multi-byte character that should have a continuation byte after it.
If you’re decoding URLs from an old PHP application, an old query log, or a system whose Content-Type predates UTF-8 dominance, the decoder needs to know the source encoding. Browsers typically default to the encoding declared in the Content-Type header or the page’s <meta charset>. Programmatic decoders default to UTF-8 and fail loudly on non-UTF-8 input.
Double-decoding
The mirror of double-encoding. If a value was URL-encoded twice on the way in (a common bug when multiple frameworks each encode “to be safe”), it must be decoded twice on the way out. Symptoms:
%2520appearing in URLs that should contain spaces. Decoded once, that’s%20. Decoded twice, that’s a space.- A URL that already looks decoded but contains
%XXsequences in the middle of words.
Best practice: encode at exactly one layer, decode at exactly one layer. If you have to debug double-encoding, the URL parser can split a URL into components so you can decode each one separately.
When decoding doesn’t help
Decoding is purely lexical. It does not validate that the result makes sense. A decoded query parameter could still be malicious input that needs further sanitization. Decoding %3Cscript%3E to <script> doesn’t make the script tag safe — it makes it visible. Server-side decoding is a step in URL handling, not a security boundary.
Common pitfalls
- Confusing decoders. Server frameworks decode the URL automatically before handing your handler the parameters. Decoding again turns
%2520into a space when the intent was%20. Know your framework’s decoding stage. - Decoding the wrong component. Paths and query strings use slightly different escape conventions. If the decoded result has stray
?or&in it, you may have decoded a value that was supposed to remain encoded for the level you were at. - Stripping
%characters that aren’t followed by hex. Better to fix the encoder than to silently drop bytes; a%without hex is an indication of upstream corruption.
For the inverse direction, see the URL encoder. For decomposing a URL into its parts, see the URL parser.