URL Encoder / Decoder with Query-String Breakdown
Split a link into scheme, host, port, path, query and fragment (international domains come out as punycode), or percent-encode and decode any text with a count of every escape. Everything runs locally in your browser.
A · Break a URL apart
| Component | Value |
|---|---|
| Scheme | — |
| Credentials user:password | — |
| Host name | — |
| Port | — |
| Path | — |
| Query string | — |
| Fragment | — |
| Normalised href | — |
Query parameters (keys and values decoded)
| # | Key | Value |
|---|
B · Percent-encode or decode a piece of text
Worked escapes
| You type | As it comes out | With structure kept | With form style |
|---|---|---|---|
a b | a%20b | a%20b | a+b |
a+b | a%2Bb | a+b | a%2Bb |
50% & 50% | 50%25%20%26%2050%25 | 50%25%20&%2050%25 | 50%25+%26+50%25 |
https://例子.com/文件 1 | https%3A%2F%2F%E4%BE%8B%E5%AD%90.com%2F%E6%96%87%E4%BB%B6%201 | https://%E4%BE%8B%E5%AD%90.com/%E6%96%87%E4%BB%B6%201 | https%3A%2F%2F%E4%BE%8B%E5%AD%90.com%2F%E6%96%87%E4%BB%B6+1 |
How links are split and re-encoded
Two link jobs that get done wrong
Two kinds of URL work are easy to get wrong. One is “what is which part of this link” — how many parameters there actually are, whether tag appears twice, whether the server ever sees the bit after #. The other is “what does this %E4%B8%AD say” — hand-encoding tends to either under-do it (you forget the & and your parameter gets sliced in half) or over-do it (you encode something already encoded and %20 becomes %2520).
So there are two sections. Section A breaks a link into scheme, credentials, host, port, path, query and fragment, and lays the query out as a parameter table. Section B percent-encodes or decodes any piece of text and tells you exactly how many escapes it wrote, and how many were invalid.
How section A parses
Parsing uses the browser’s own URL constructor (the WHATWG URL standard) rather than a regex I invented. That choice produces several behaviours that surprise people, and all of them show up honestly in the output:
- International domains come out as punycode.
https://例例.中国/ayields the host namexn--fsqa.xn--fiqs8s. That is IDNA conversion, not a parsing bug. Compare host names and make requests with that value; keep the original for humans. - The default port disappears.
https://a.com:443/preports no explicit port, because 443 already is the https default.http://a.com:8080reports 8080 normally. - Whitespace and control characters get handled. A space inside the path turns into
%20, leading and trailing whitespace is dropped, and tabs or newlines inside the link are deleted. Paste a link out of a document here first to see what the browser will actually request. - The href row is the normalised link. If it differs from your input, the link was tidied up:
.and..segments collapsed, scheme lower-cased, and backslashes treated as/for schemes like http and https (https:\\a.combecomeshttps://a.com/). - A missing scheme is filled in with
https://. Counting colons is not enough to detect one: handexample.com:8080/x/../ystraight tonew URL()and it treatsexample.comas the scheme name and8080/x/../yas the path, quietly giving you a wrong answer. So only “scheme +//” is accepted, and anything else (example.com/a?b=1,//cdn.example.com/x.js) getshttps://prepended, with a note saying so — the assumed scheme and port are guesses, not facts about your original link. Schemes that legitimately have no//(mailto:,tel:,data:,javascript:) are parsed as written, and an empty host name for them is normal. - The fragment never reaches the server. Everything after
#is client-side. If a “parameter” is missing from server logs, check whether it lives after the hash.
How the query string is split
Everything after ? is cut on &; inside each segment the first = separates key from value (further = stay in the value). Three details regularly disagree with intuition:
- A segment with no
=is a parameter with an empty value.?debugis one parameter, keydebug, value empty. Its key is shown in bold and counted in the note. - A semicolon is not a separator.
?a=1;b=2is one parameter with the value1;b=2under WHATWG rules. The tool flags it, because plenty of older backends (some PHP configurations, old Java containers) do split on;— for those links the answer is the server’s, not the parser’s. - Repeated names stay repeated.
?tag=1&tag=2is two parameters, listed one per row and never merged. The count lives in the Parameters stat.
Keys and values are then percent-decoded individually. The +-as-space box is checked by default, which is the application/x-www-form-urlencoded convention used by forms and most query strings. Uncheck it for links that use %20 for spaces — static asset URLs, typically — where + means a literal plus.
How section B encodes
Encoding is character by character: each character is expanded into UTF-8 bytes and each byte is written as % plus two upper-case hex digits. Which means:
- One Chinese character becomes nine. It occupies three UTF-8 bytes, three characters each →
%E4%B8%AD. An emoji takes four bytes → twelve characters, and in JavaScript it is two character positions (a surrogate pair). Iteration is by code point, so a pair never gets split in half and encoded byte-wise. - The safe set is
A-Za-z0-9-._~plus!'()*. The first group is RFC 3986’s unreserved set. The last four areencodeURIComponent’s legacy from RFC 2396.encodeURIComponentis followed deliberately so the output matchesencodeURIComponent(x)in your code character for character — a one-off mismatch there costs an afternoon. - “Keep URL structure characters” is the
encodeURIconvention::/?#[]@!$&'()*+,;=pass through. Useful when you want a whole link lightly escaped; wrong for parameter values, where&,=and#must be escaped or they change the link’s shape. Off by default, i.e. theencodeURIComponentconvention. +only means a space in form mode. Checked while encoding, a space is written as+; checked while decoding,+becomes a space. In a path,+was never a space.
Where the results stop being trustworthy
- Decoding does not throw, but it may not be clean. A stray
%(as in100%pure) is not an error: it is kept literal and counted. The real problem is invalid UTF-8 —%C3%28, where the bytes do not form a sequence. Decoding does not fail there; one U+FFFD is substituted per bad sequence and counted. So “decoded” is not the same as “your input was valid”: if the count is not zero, go back to the source. - Double encoding is yours to spot. Decoding
%25E4%25B8%25ADonce gives%E4%B8%AD, which looks like “one more to go”. The tool decodes exactly once and does not guess. Check whether the output still contains well-formed%XXgroups. - Section A checks syntax, not safety.
https://a.com/../../../../etc/passwdnormalises tohttps://a.com/etc/passwd, because collapsing dot segments is part of the URL standard. That does not make the link safe — path traversal is the server’s business. Genuinely malformed input, such as port:99999, fails to parse. - The parameter table lists the first 200 rows only. A link with a thousand parameters is usually a concatenation bug; the note reports the real total.
- Relative references are not resolved.
./a/b?c=1and//cdn.example.com/x.jsneed a base URL. Make them absolute first, or resolve them against the site you copied them from.
Questions people ask
Why is my encoded string different from another site’s output?
Two likely reasons. Spaces: other tools may emit + where %20 is what comes out by default (tick “form style” to match). And !'()*: many online encoders follow RFC 3986 strictly and escape them as %21%27%28%29%2A, while the escaping here follows encodeURIComponent and leaves them alone. Both decode correctly on the server, but if you are comparing byte-for-byte with some system’s output — a signature, say — pick one convention and stick to it.
Should + be escaped?
In a query string: if the string came from form encoding, + is a space and a literal plus must be written %2B. If it came from encodeURIComponent, a space is %20 and + is a plus. Both are legal URLs; only the decoder on the other end differs. That is exactly why + is a checkbox instead of a guess.
Does the link I paste get uploaded?
No. Parsing uses the in-browser URL object and the escaping is a loop in the browser-side script — the links you paste are never handed to anybody else, so they do not leave this device.
Why does section A need a button while other tools recompute on every keystroke? A half-typed URL is always invalid, so live parsing would spend the whole time showing errors. Section B is a conversion, not a judgement, so it sits behind buttons too. Once you have parsed a complete link, editing it re-runs the parse automatically.