Skip to content

URL Decode

Decode a percent-encoded URL string.

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:

  1. A % not followed by two hex digits. hello%world is invalid — the decoder expected hex after %. Strip or escape stray % characters before decoding.
  2. 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., %C3 followed 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:

  • %2520 appearing 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 %XX sequences 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 %2520 into 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.