URL Parser
Split a URL into protocol, host, port, path, query and hash, and read every parameter raw and decoded.
Absolute URLs only — the scheme is required. Parsed on every keystroke, in the page.
https://api.example.com
- Protocol
- https:
- Username
- none
- Password
- none
- Hostname
- api.example.com
- Port
- 443implied, not written
- Pathname
- /v1/search
- Search
- ?q=iced%20coffee&tag=caf%C3%A9&tag=latte&page=2
- Hash
- #results
- Origin
- https://api.example.com
- 1v1
- 2search
Split on dots, last label first. This is not the registrable domain — that needs the Public Suffix List, so a host like shop.example.co.uk reports domain co and TLD uk.
| Key | Raw | Decoded | Copy |
|---|---|---|---|
| q | iced%20coffee | iced coffee | |
| tag | caf%C3%A9 | café | |
| tag | latte | latte | |
| page | 2 | 2 |
One row per occurrence, in source order. A repeated key is not collapsed — that is what URLSearchParams.getAll returns, and get would hand back only the first.
scheme://username:password@hostname:port/path?key=value#fragment
The subdomain, domain and TLD split is a plain split on dots, not the Public Suffix List — shop.example.co.uk reports domain co and TLD uk. A password is masked on screen only; it is still in the box you pasted it into.
How it works
Paste a full URL, scheme included. A bare example.com/path is a relative reference and will not parse on its own — the parser is told when that is what happened.
Parsing runs through the WHATWG URL parser built into the browser, so the answer matches what fetch, curl and the address bar see, normalisation and all: lowercased scheme and host, resolved ./ and ../ segments, punycoded international domains, and default ports dropped.
Read the parts list: protocol, credentials, hostname, port, path, query, fragment and origin. A port that was left off is shown as the implied default — 443 for https, 80 for http.
The query table lists one row per parameter in source order, with the raw text and the percent-decoded value side by side. Repeated keys stay on separate rows, which is what URLSearchParams.getAll returns.
Copy any decoded value from its row. Everything runs in the page — the URL is never uploaded, logged or stored, which matters because URLs carry tokens.
The shape of a URL
A URL is a scheme, an optional userinfo pair, a host, an optional port, a path, an optional query and an optional fragment. The query is decoded as application/x-www-form-urlencoded — pairs split on &, key and value split at the first =, + read as a space and %XX read as a byte. That plus-is-a-space rule applies to the QUERY ONLY: a + in a path segment is a literal plus, which is why the path and the query are decoded differently here.
scheme://username:password@hostname:port/path?key=value&key=value#fragment
Use cases
Reading UTM and ad-click parameters
utm_source, utm_medium, utm_campaign, utm_content, utm_term, plus gclid and fbclid. Campaign names arrive percent-encoded or plus-separated, so "Spring%20Sale%202026" and "spring+sale" both need decoding before they read as the string the analytics tool will group on.
Debugging an OAuth redirect
A callback URL carries a URL inside a URL: redirect_uri is percent-encoded whole, and doubly encoded whenever something re-encoded an already-encoded value. Paste the callback, read redirect_uri decoded, and check state and code_challenge came back the way they went out.
Repeated keys and array parameters
tag=a&tag=b is two rows here, not one — the wire format has no arrays, so a repeated key is just a repeated key. get() returns the first, getAll() returns both. PHP wants tag[], Rails wants tag[] too, Express with the default parser accepts either; the URL itself takes no position.
Which port is actually being used
The URL Standard removes a default port during parsing, so https://example.com:443/ normalises to https://example.com/ and .port comes back empty. The implied value is shown instead of a blank: 80 for http and ws, 443 for https and wss, 21 for ftp. Any other scheme has no default at all.
Splitting a host into subdomain, domain and TLD
The split here is by dot label, which is right for api.example.com and wrong for shop.example.co.uk — that one reports domain "co", TLD "uk". A correct registrable-domain split needs the Public Suffix List, a ~9,000-entry file that changes weekly, and this tool does not carry one.
Path segments for routing
The path is split on / BEFORE anything is decoded, which is the order that matters: a %2F inside a segment stays inside that segment instead of silently becoming a route boundary. Each segment is then percent-decoded on its own, so /files/report%202026.pdf reads as two segments and not three.
Fragments never reach the server
Everything after # is stripped by the client before the request goes out, so it appears in no access log and no server-side handler. That is why the OAuth implicit flow put tokens there, and why a fragment is the one part of a URL you cannot debug from the server side.
URL length limits
There is no limit in the spec and several in practice: old Internet Explorer capped at 2,083 characters, nginx rejects a request line past large_client_header_buffers (8 KB by default), and Apache past LimitRequestLine (8,190). The parsed length is reported so a long query string can be measured before it 414s.
Credentials in a URL
https://user:pass@host is legal syntax and a bad idea — RFC 3986 §3.2.1 deprecates it, browsers strip or warn on it, and proxies log the whole request line. The password is masked on screen here, though it is still sitting in the box you pasted it into.
Questions
- Why does my URL fail to parse?
- It is almost always a missing scheme. new URL() takes an absolute URL; example.com/path is a relative reference and needs a base to resolve against, and there is no sensible base for a paste box. Add https:// and it parses. A space in the host or a stray character in the authority will also fail.
- Is the subdomain, domain and TLD split reliable?
- No, and this is the one number here not to trust. It is a plain split on dots: the last label is called the TLD, the one before it the domain, everything else the subdomain. That is correct for api.example.com and wrong for shop.example.co.uk, which reports domain "co" and TLD "uk". Getting it right means checking the host against the Public Suffix List, which is a maintained file of every multi-label suffix, and this tool does not ship one.
- Why does the port show 443 when I did not type one?
- Because that is the port the request will use. The URL Standard strips a default port during parsing, so .port comes back empty for https://example.com and for https://example.com:443 alike. The implied value is shown separately and marked as implied, rather than leaving a blank that reads as "no port".
- Why show both the raw and the decoded value?
- They are different strings and different things depend on each. A webhook or OAuth signature is computed over the raw query text, so comparing a decoded value against a signed one fails for reasons that are invisible if you only see the decoded form. The decoded value is what the application receives.
- What happens to repeated keys?
- Each occurrence gets its own row, in source order. Collapsing them would lose data — ?tag=a&tag=b is two values, and URLSearchParams.getAll returns both while get returns only the first.
- Is the password really hidden?
- It is masked in the parts list only. The URL you pasted is still in the input box, and nothing here is encryption. What is true is that no part of it leaves the browser: there is no request, no logging and no storage in this page.
- Does it handle international domains and punycode?
- Yes, in the direction the parser goes. An IDN host is converted to its A-label during parsing, so café.example becomes xn--caf-dma.example and that is what the hostname field shows — which is the form that actually goes into the DNS query.
More tools
One of 18 free tools here. They come out of building utility apps for macOS, iOS and the browser — all of it by one person.
See the apps