What is URL decoding?
URL decoding is the reverse of URL encoding — it converts percent-encoded sequences back into their original characters. A percent sign followed by two hex digits (%XX) is replaced by the UTF-8 character that byte represents. For example, hello%20world decodes to hello world, and email%40example.com decodes to [email protected].
URL decoding is necessary in several day-to-day development tasks: reading analytics query strings, debugging API requests in server logs, parsing OAuth redirect URIs, and understanding webhook payloads. Browsers decode URLs automatically in the address bar, but application code often receives the raw encoded form.
decodeURIComponent vs decodeURI
The two JavaScript decode functions differ in which encoded sequences they will decode:
| Function | Will NOT decode | Use for |
|---|---|---|
| decodeURIComponent | Nothing — decodes all %XX sequences | Query parameter values, path segment values |
| decodeURI | %2F %3F %23 %3A %40 (URL structural chars) | Full URLs — preserves routing structure |
For most debugging tasks, decodeURIComponent is what you want — it decodes everything and shows the full human-readable form. Use decodeURI only when you need to decode a full URL while keeping its structure intact for routing purposes.
Common decoding scenarios
Reading a search query from server logs:
// Log entry: GET /search?q=best%20laptop%20under%20%241000
const params = new URLSearchParams(queryString);
const query = params.get('q');
// → "best laptop under $1000" Decoding an OAuth redirect URI:
// Encoded: redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
const raw = 'https%3A%2F%2Fapp.example.com%2Fcallback';
const decoded = decodeURIComponent(raw);
// → "https://app.example.com/callback" Parsing multi-byte Unicode characters: Characters like Japanese, Arabic, or emoji are encoded as multiple %XX sequences because they require multiple UTF-8 bytes. For example, 😀 encodes as %F0%9F%98%80. decodeURIComponent handles multi-byte sequences automatically.
Understanding double encoding
Double encoding is one of the most common URL debugging problems. It happens when a value is encoded more than once:
// Step 1: normal encoding
"hello world" → "hello%20world"
// Step 2: accidentally encode again
"hello%20world" → "hello%2520world"
// ^^ %25 is the encoding of %, so %20 → %2520
// Decoding once gives %20, not "hello world"
decodeURIComponent("hello%2520world") → "hello%20world" // wrong
decodeURIComponent("hello%20world") → "hello world" // correct Double encoding typically occurs in these situations:
- A URL is built by string concatenation instead of using
encodeURIComponenton individual values - A redirect target URL is itself embedded as a query parameter without encoding the already-encoded URL again
- A middleware layer or framework encodes values before they reach your application code, which then encodes them a second time
URL decoding in other languages
| Language | Function | Notes |
|---|---|---|
| Python 3 | urllib.parse.unquote(s) | Also unquote_plus() for + → space |
| Python 2 | urllib.unquote(s) | Does not handle Unicode — prefer Python 3 |
| Java | URLDecoder.decode(s, "UTF-8") | java.net package; always specify UTF-8 |
| PHP | urldecode($s) / rawurldecode($s) | rawurldecode follows RFC 3986 (%20 → space) |
| Ruby | URI.decode_www_form_component(s) | CGI.unescape also available |
| Go | url.QueryUnescape(s) | net/url package; + decoded as space |
| C# | Uri.UnescapeDataString(s) | System namespace; does not decode + |
| Rust | urlencoding::decode(s) | urlencoding crate |
Frequently Asked Questions
Why does my decoded URL look wrong — what is double encoding?
Double encoding happens when a value that is already percent-encoded is encoded a second time. For example, a space becomes %20 after one encoding. If %20 is then encoded again, the % itself becomes %25, giving %2520. Decoding %2520 once produces %20 (not a space), which looks wrong. To fix it, decode the string twice. Double encoding is common when a URL is constructed by string concatenation rather than using encodeURIComponent properly, or when a redirect target URL is embedded inside another URL.
What causes a 'Malformed URI' error when decoding?
A malformed URI error means the input contains a percent sign (%) that is not followed by two valid hexadecimal digits. This can happen if the string was partially encoded, if the percent was a literal percent sign that was not itself encoded (should be %25), or if the input is binary data rather than a URL. The fix depends on the source: if the % was meant as literal data it should be %25; if the encoding was truncated or corrupted you need to trace back to the source.
How do I decode a query string with multiple parameters?
Paste the entire query string (everything after the ?) and the decoder will handle the full string. If you want to split it into key-value pairs afterwards, use URLSearchParams in JavaScript: new URLSearchParams(window.location.search) parses and decodes the current page's query string automatically. You can then call .get('key') or iterate with .entries() to access individual values. The browser handles decoding for you when you use these APIs.
What is the difference between decodeURIComponent and decodeURI?
decodeURIComponent decodes all percent-encoded sequences, including those representing URL structural characters like %2F (slash), %3F (question mark), %23 (hash). Use this when decoding a value that came from inside a URL component. decodeURI does not decode sequences that represent reserved characters (%, /, ?, #, etc.) because decoding those would change the URL's structure. Use decodeURI only when you want to make a full URL human-readable without changing its routing behaviour.
How do I decode a URL in Python, Java, or other languages?
Python: urllib.parse.unquote(encoded_string) — for Python 2 use urllib.unquote(). Java: java.net.URLDecoder.decode(encoded, 'UTF-8'). PHP: urldecode($encoded) decodes form-encoding (+ to space); rawurldecode($encoded) follows RFC 3986 (%20 to space). Ruby: URI.decode_www_form_component(encoded). Go: url.QueryUnescape(encoded). All of these follow the same percent-encoding standard so results will match this tool.