When you need to decode a URL
A URL like https://example.com/search?q=hello%20world&sort=price%3Aasc&tags%5B%5D=js&tags%5B%5D=react looks dense and confusing. Decoding it reveals: q=hello world, sort=price:asc, tags[]=js, tags[]=react. That transformation — from encoded to readable — is what URL decoding does.
You need to decode URLs in several situations: debugging a broken link someone sent you, understanding what parameters an analytics tool injected, reverse-engineering a competitor’s search URL structure, or copying a URL from browser DevTools and reading it in a log. The fastest way to do any of these is the Query String Builder in Parse mode, but understanding what is happening under the hood makes you a better debugger.
The anatomy of an encoded URL
A URL has five parts, each with different encoding rules:
https://example.com/path/to/page?key=value&key2=value2#section
[scheme] [host] [path] [query string] [fragment]
The query string starts at the first ? and ends at # or the end of the URL. It consists of key=value pairs separated by &. Both keys and values can be percent-encoded. The fragment (#section) is never sent to the server — it stays in the browser.
How percent-encoding works
Percent-encoding (also called URL encoding) replaces a character with a % followed by its two-digit uppercase hexadecimal UTF-8 byte value. A space is byte 0x20, so it becomes %20. The colon : is byte 0x3A, so it becomes %3A. Square brackets [ and ] become %5B and %5D.
To decode a percent-encoded string manually:
- Find every
%XXsequence in the string - Convert the two hex digits to a byte value (
XXin hex) - Collect consecutive bytes that form multi-byte UTF-8 sequences
- Decode the bytes as UTF-8 to get the original character
In practice, no one does this by hand for more than a character or two. Use a tool.
The + space ambiguity
The single biggest source of URL decoding confusion is the + character. Here is the rule:
- In form-urlencoded data (HTML
<form method="get">submissions),+represents a space. This is a legacy behaviour from very early web specifications. - In all other URL contexts — the path, the fragment, or a manually constructed query string —
+is a literal plus character. %20always means a space, in all contexts.%2Balways means a literal+, in all contexts.
When you copy a URL from a search bar after typing a search with spaces, you will often see + as the separator (because the form submitted with + spaces). When you manually build a URL or use an API, use %20.
The Query String Builder decodes both %20 and + as spaces when parsing (since most real-world URLs use either), and always generates %20 when building (safer and unambiguous).
Decoding in JavaScript
JavaScript gives you several built-in tools:
// Decode a full URL (does NOT decode reserved chars like & = ? /)
decodeURI("https://example.com/search?q=hello%20world");
// Decode a single parameter value (decodes everything including & = /)
decodeURIComponent("hello%20world%26more"); // → "hello world&more"
// Parse a query string into a URLSearchParams object
const params = new URLSearchParams("?q=hello%20world&sort=asc");
params.get("q"); // → "hello world"
params.get("sort"); // → "asc"
// Iterate all parameters
for (const [key, value] of params) {
console.log(key, value);
}
// Parse from a full URL
const url = new URL("https://example.com/search?q=hello%20world&tags[]=js&tags[]=react");
const urlParams = new URLSearchParams(url.search);
urlParams.getAll("tags[]"); // → ["js", "react"]
Use decodeURIComponent() for individual parameter values — it handles everything including %2F (slash) and %26 (ampersand). Use URLSearchParams when you want to parse a full query string.
Decoding in Python
from urllib.parse import urlparse, parse_qs, unquote, unquote_plus
# Decode a percent-encoded string (does not convert + to space)
unquote("hello%20world") # → "hello world"
# Decode with form-encoding (converts + to space)
unquote_plus("hello+world") # → "hello world"
# Parse a query string into a dict (values are always lists)
parse_qs("q=hello%20world&tags=js&tags=react")
# → {"q": ["hello world"], "tags": ["js", "react"]}
# Parse from a full URL
from urllib.parse import urlparse
parsed = urlparse("https://example.com/search?q=hello%20world")
query = parse_qs(parsed.query)
Decoding in PHP
// Decode a percent-encoded string
urldecode("hello%20world"); // → "hello world"
rawurldecode("hello%20world"); // → "hello world" (RFC 3986)
// Decode + spaces (form-encoded)
urldecode("hello+world"); // → "hello world"
// PHP automatically parses query strings into $_GET
// Access them directly: $_GET["q"], $_GET["tags"][]
// Manual parsing
parse_str("q=hello%20world&tags[]=js&tags[]=react", $params);
print_r($params);
// Array ( [q] => hello world [tags] => Array ( [0] => js [1] => react ) )
Debugging broken links step by step
When a link is broken or behaving unexpectedly, work through this checklist:
Step 1 — Paste into the Query String Builder. Use Parse mode in the Query String Builder to split the URL into its decoded key-value pairs. Often the problem is immediately visible: a missing parameter, a double-encoded value, or a key that has a typo.
Step 2 — Look for double encoding. Double encoding happens when you encode an already-encoded string. A space becomes %20, then the % gets encoded to %25, producing %2520 — which the server decodes as %20 (literally) rather than a space. If you see %25 in a URL, something encoded a string that was already encoded.
Step 3 — Check the encoding of special characters. If your value contains &, =, #, or + literally, they must be encoded as %26, %3D, %23, and %2B respectively. If they appear unencoded, the parser will misinterpret them as URL structure rather than data.
Step 4 — Inspect in DevTools. Open browser DevTools (F12), go to the Network tab, trigger the request, and click it. Look at the “Query String Parameters” section in the Headers tab — the browser shows both the encoded and decoded versions.
Step 5 — Validate on the server. Add server-side logging to print the raw incoming query string and the parsed parameters. This reveals whether the encoding problem is in the link generation, the browser, a proxy, or the parser.
Multi-byte characters (Unicode/emoji)
Non-ASCII characters — accented letters, Cyrillic, Chinese, Arabic, emoji — are first encoded as UTF-8 bytes, then each byte is percent-encoded.
The letter é in UTF-8 is two bytes: 0xC3 0xA9. In a URL: %C3%A9.
The emoji 🎯 in UTF-8 is four bytes: 0xF0 0x9F 0x8E 0xAF. In a URL: %F0%9F%8E%AF.
Modern browsers, frameworks, and encodeURIComponent() / decodeURIComponent() all handle this automatically. The only risk is using older tools that assume single-byte Latin-1 encoding — these will mangle non-Latin characters.
When to build vs parse
The Query String Builder works in two directions:
Build mode is for when you know the keys and values and need to generate the encoded query string or full URL. Enter your pairs, choose RFC-3986 or form-encoded, and copy the result.
Parse mode is for when you have a URL or query string in the wild and want to see the decoded pairs. Paste it in and the tool shows each key decoded, each value decoded, flags duplicate keys, and handles both %20 and + spaces.
For the common developer task of debugging what a URL actually says, Parse mode is the fastest path from confusion to clarity.