Paste percent-encoded text and read it. This is usually a query
parameter or a redirect target. If the output still shows
sequences like %3A%2F%2F, the input was encoded
twice (%253A%252F%252F), so paste the output back in
and decode once more.
https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello%20world%26lang%3Den https://example.com/search?q=hello world&lang=en OAuth and SSO flows nest a whole URL inside a query parameter, such as redirect_uri, encoded once or twice. Decode it to check where the flow sends the user.
Spaces arrive as %20 or +, and which one you see tells you how the request was built. The decoder follows decodeURIComponent, so + stays a plus sign.
const encoded = "hello%20world%20%26%20more%2F100%25";
console.log(decodeURIComponent(encoded));
// hello world & more/100%
// Form data uses + for spaces: swap them before decoding
const fromForm = "first+name=John+Doe";
console.log(decodeURIComponent(fromForm.replace(/\+/g, " ")));
// A stray % throws URIError: URI malformed
try {
decodeURIComponent("100%");
} catch (err) {
console.error(err.name, err.message);
} Always wrap decodeURIComponent() in try/catch when the input comes from users or logs.
<?php
$encoded = "hello%20world%20%26%20more%2F100%25";
// Decodes %XX only; a + stays a +
echo rawurldecode($encoded), PHP_EOL;
// Decodes %XX and turns + into a space (form data)
echo urldecode("first+name=John+Doe"), PHP_EOL;
// Decode a full query string into an array in one step
parse_str("q=hello%20world&tags%5B%5D=a&tags%5B%5D=b", $params);
print_r($params); PHP never throws on bad sequences. An invalid % is passed through unchanged.
from urllib.parse import unquote, unquote_plus
encoded = "hello%20world%20%26%20more%2F100%25"
# %XX only; + is left alone
print(unquote(encoded))
# %XX and + to space, for form-encoded data
print(unquote_plus("first+name=John+Doe"))
# Python never raises on bad sequences: a lone % is returned as-is
print(unquote("100%")) Pass errors="strict" to unquote() if you want a UnicodeDecodeError on invalid UTF-8 instead of replacement characters.
Split a URL on its delimiters before you decode it. decodeURIComponent() turns %2F into a slash even though encodeURI() would never have produced it, so decoding a whole URL in one pass can create path separators out of what was data. Split first, then decode each piece.
Decode once, as late as possible. Express, Laravel and Django all decode query values and route parameters before your handler runs, so decoding those again is where most double-decoding bugs come from. If a string already reads as plain text with a stray % in it, it has probably been decoded already, so leave it alone.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
It converts percent-encoded sequences such as %20 and %C3%A9 back into the original characters (a space and é), so an encoded query value becomes readable text.
A value was encoded twice, which turns %20 into %2520. Decode once to get %20 and once more to get the space. Then fix the code that encodes twice, usually a manual encode followed by an HTTP client that encodes again.
No. Plus signs are left as they are, matching decodeURIComponent(). If the string came from a form submission, replace + with a space before you decode.
A percent sign not followed by two hex digits, such as a literal % in 50% off, or bytes that do not form valid UTF-8. Encode the stray percent as %25 at the source.
No. The tool calls decodeURIComponent() in your browser tab, and uploaded files are read locally, so nothing is sent to a server.
The URL encoder goes the other way. To see the parameters as a list instead of one long string, use the query string parser or the URL parser.