Command Palette

Search for a command to run...

URL Parser: Break Any Link Into Its Parts and Read the Query String

T
Toolz Team
|Jul 22, 2026|15 min read

I lost an afternoon once to a URL that looked fine. An OAuth callback kept failing, the redirect URI "matched" the one registered with the provider, and I could not see why the handshake broke. The answer, when I finally pasted the thing into a parser, was a trailing slash on the path in one place and none in the other, plus a state parameter that had been double-encoded so %20 had become %2520. To the human eye the two URLs were identical. To the OAuth server they were different strings, and it was right to reject the mismatch.

That is the problem with URLs: they are dense, they are easy to misread, and the details that break things β€” an encoded slash, a stray port, a repeated query key, a fragment where you expected a path β€” are exactly the ones that hide in a wall of characters. I build toolz.dev, and I spend enough time staring at query strings while debugging that I built a URL Parser to do the staring for me. Paste a link, get every component labelled and every query parameter decoded in a table. This guide explains what those components are, why the distinctions matter, and how to use them.

TL;DR: A URL is made of a scheme (https), optional credentials (user:pass@), a host (example.com) with an optional port, a path (/blog/post), a query string (?id=42), and a fragment (#section). The URL Parser splits any link into those parts using the browser's own WHATWG URL engine, decodes the query into an ordered key-value table (repeated keys kept separate), shows the effective port for the scheme, and assumes https:// if you paste a bare domain. It runs entirely in your browser, so links with tokens stay private.

What are the parts of a URL?

Every URL follows the same grammar, defined by the WHATWG URL Standard β€” the specification browsers actually implement. Once you can name the parts, most URL bugs become obvious. Here is the full anatomy, using a deliberately busy example:

https://john:[email protected]:8443/catalog/shoes?color=red&size=42#reviews
β””β”€β”¬β”€β”˜   β””β”€β”€β”¬β”€β”€β”˜β””β”€β”¬β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”¬β”€β”˜β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”¬β”€β”€β”˜
scheme   user  pass       hostname     port      path          query      fragment

Breaking that down:

Component Example value What it is
Scheme https The protocol. Decides default port and how the request is made.
Username john Optional credential, before the @.
Password s3cret Optional credential, after the : in the userinfo.
Hostname shop.example.co.uk The domain or IP address, with no port.
Port 8443 Optional. Falls back to the scheme default when omitted.
Host shop.example.co.uk:8443 Hostname plus port, when a port is present.
Origin https://shop.example.co.uk:8443 Scheme plus host β€” the unit browsers use for security.
Path /catalog/shoes The resource location on the host.
Query ?color=red&size=42 Key-value parameters after the ?.
Fragment #reviews Client-side anchor after the #, never sent to the server.

The parser lays every one of these out as its own row with a copy button, so you never have to hand-extract a hostname from a monster URL again. It also flags a couple of things the raw string hides: whether the port shown is explicit or the scheme's default, and whether the host is a named domain or a raw IP.

What is the difference between hostname, host, and origin?

These three trip people up constantly, and the confusion causes real bugs β€” CORS failures, cookie scoping mistakes, redirect mismatches. They are not synonyms.

Hostname is just the domain or IP: shop.example.co.uk. No port, no scheme. It is what you would put in a DNS lookup.

Host is the hostname plus the port, but only when a port is present in the URL. For shop.example.co.uk:8443 the host is shop.example.co.uk:8443. For a plain https://shop.example.co.uk/ the host and hostname are identical, because the default port 443 is implied rather than written. That "only when present" rule is subtle and is why the same site can appear to have two different hosts.

Origin is scheme plus host: https://shop.example.co.uk:8443. This is the one browsers care about most, because the same-origin policy β€” the foundation of web security β€” compares origins, not hostnames. Two URLs share an origin only if their scheme, hostname, and port all match. http://example.com and https://example.com are different origins because the scheme differs. https://example.com and https://example.com:8443 are different origins because the port differs, even though the hostname is the same. If a fetch is failing with a CORS error, comparing the two origins side by side in the parser is usually the fastest way to spot the mismatch.

How do I parse a query string?

The query string is where most of the day-to-day pain lives, because it is a flat, unescaped-looking blob that is actually structured and percent-encoded. The parser splits it for you: everything after the ?, broken on &, with each key=value pair decoded and listed in a table in its original order.

Two behaviours matter here. First, decoding. A parameter written as q=trail%20runner on the wire is shown as trail runner in the value column, because %20 is a percent-encoded space. The raw search string is still shown untouched in the components list, so you can compare the encoded and decoded forms β€” invaluable when you suspect double-encoding, like my %2520 OAuth bug.

Second, repeated keys. A URL can legitimately carry the same key more than once: ?tag=react&tag=typescript&tag=node. Many naive parsers collapse these, keeping only the first or last value and silently losing data. That is wrong β€” repeated keys are how HTML forms submit multi-select fields and how a lot of APIs express arrays. The parser keeps every occurrence as its own row, in order, so you see all three tags. When you copy the query as JSON, repeated keys become an array, which is the shape most code expects.

You do not even need a full URL to use this. Paste just a query string β€” color=red&size=42 β€” and the tool parses it on its own. It is the fastest way I know to make sense of a webhook payload or a tracking link someone forwarded you.

How do I use the URL Parser?

The tool is designed to get out of your way. Paste a URL into the single input and it parses live as you type β€” no button to press. A sample link is pre-loaded so you can see the full breakdown immediately, and a Clear button empties the field.

You do not have to type the scheme. Paste a bare host like example.com/pricing and the parser prepends https:// automatically, then tells you it did so with a small note, so you are never confused about where the scheme came from. Paste an explicit scheme β€” http://, ftp://, ssh:// β€” and it respects that instead.

The output has four zones. At the top, the normalised URL β€” the canonical form the browser's engine produced, with a copy button, which is handy for catching subtle normalisation differences. Below that, the components table, one labelled row per part, each independently copyable. Then path segments, broken into indexed chips so a deep path like /api/v2/users/42/orders is legible at a glance. Finally the query parameters table, decoded and ordered, with a "copy as JSON" action that turns the whole query into a clean object.

Everything runs in your browser using its native URL engine. That is a deliberate choice: URLs routinely contain access tokens, session IDs, signed parameters, and internal hostnames, and none of that should be shipped to a server just to be read. Nothing you paste leaves your device, and the tool keeps working offline. It is the same privacy-first approach behind the whole toolkit, which I go into in the web developer toolkit guide.

When do I reach for a URL parser?

A few situations come up again and again in my own work. Debugging redirects and callbacks is the big one β€” OAuth flows, payment return URLs, SSO handshakes, all of which fail on tiny mismatches that only become visible when you decompose both URLs. Auditing tracking links is another: marketing URLs are often a base page plus a dozen UTM and ad-platform parameters, and reading them as a table beats squinting at a 300-character string. If you are building those links rather than reading them, the UTM Builder is the other half of the same workflow.

Then there is API work β€” inspecting the query parameters a client actually sent, or reverse-engineering how an endpoint expects its filters. And security review: an unfamiliar link in an email or a log is much safer to understand by parsing its parts (what host does this really point at? is that hostname an IP?) than by clicking it. The parser exposes the true hostname and flags IP-literal hosts, which is exactly the information you want before trusting a link. I wrote more about assembling this kind of inspection kit in the API debugging tools guide.

How does parsing relate to encoding and slugs?

A URL parser is one corner of a small family of link tools, and knowing which one you need saves time. Parsing reads an existing URL and pulls it apart. Encoding does the opposite direction at the character level β€” turning spaces and special characters into their percent-encoded forms so they survive inside a URL, and back again. When you need to safely embed a value into a query string, or decode one that is mangled, that is the URL Encoder/Decoder, and it pairs naturally with the parser: parse to see the structure, encode to fix a broken value.

Slug generation is a third, related job β€” taking a human title like "10 Tips for Faster Builds" and turning it into a clean 10-tips-for-faster-builds path segment. That is what the Slug Generator handles, and it is what produces the tidy path component the parser later reads back. Think of it as a pipeline: slugify to build good paths, encode to make values URL-safe, parse to inspect the finished link. Each tool does one part of the URL lifecycle and does it in the browser.

What about IP addresses and internationalised domains?

Not every host is a tidy example.com. Some URLs point at raw IP addresses, and the parser recognises both forms. An IPv4 literal like http://192.168.1.10:3000/ has a hostname of 192.168.1.10, and the tool flags it as an IP rather than a domain β€” useful when you are auditing a link and want to know instantly whether it targets a named site or a bare address, which is a common signal in suspicious links. IPv6 literals are wrapped in square brackets in a URL, as in http://[2001:db8::1]:8080/, and the brackets are part of the host syntax, not decoration; the parser handles that bracketed form correctly instead of choking on the colons, which would otherwise look like port separators.

Internationalised domain names are the other edge case. A host written in non-ASCII characters β€” say a domain with accented or non-Latin letters β€” is converted by the browser's URL engine into its Punycode xn-- form for the actual request, because DNS only speaks ASCII. Seeing the normalised href in the parser shows you exactly what the browser will resolve, which occasionally surprises people who expected their pretty Unicode domain to travel unchanged. For the top-level domain, the parser extracts the final label of a named host, so shop.example.co.uk reports a TLD of uk. That is a deliberately simple rule β€” it does not try to unpick multi-part suffixes like .co.uk into a registrable domain, because doing that properly requires the Public Suffix List, which is a large moving dataset. For quick inspection the last label is the useful signal, and for anything more rigorous you would reach for a dedicated library.

A worked example ties it together. Say a payment provider keeps rejecting your return URL. You registered https://app.example.com/checkout/return but the failing request shows https://app.example.com:443/checkout/return/. Parse both. The parser shows the first has host app.example.com (default port, no trailing slash on the path) and the second has host app.example.com too β€” but its path is /checkout/return/ with a trailing slash, and its port was written explicitly as :443. Two differences the eye glides over, both fatal to an exact-match check. Once you can see them as separate labelled components, the fix is obvious: normalise the trailing slash and drop the redundant explicit port.

Common mistakes when reading URLs

The recurring errors are worth naming. Confusing the fragment with the path or query β€” everything after # is the fragment, it is handled entirely by the browser and is never sent to the server, so a parameter you put after # will not reach your backend. Assuming a missing port means no port β€” an omitted port means the scheme default (443 for https, 80 for http), which the parser makes explicit so you know which port a request will really hit.

Ignoring double-encoding β€” if a value looks like %2520 instead of %20, it was encoded twice; parse it, and if the decoded value still contains a percent sequence, decode again. Trusting the visible text of a link β€” the text you see and the actual href can differ completely, which is the whole mechanism behind phishing; parsing reveals the real destination host. And treating repeated query keys as duplicates to discard β€” they are often meaningful arrays, and dropping them loses data.

Frequently asked questions

What are the parts of a URL?

A URL has a scheme (https), optional credentials (user:pass@), a host (example.com) with an optional port, a path (/blog/post), an optional query string (?id=42), and an optional fragment (#section). This parser separates and labels each one.

How do I parse a query string?

Paste the full URL and read the query table, or paste just the query string on its own. The parser splits it on ampersands, decodes percent-encoding, and lists every key-value pair in order. Repeated keys such as tag=a&tag=b are kept as separate rows.

What is the difference between hostname, host, and origin?

Hostname is just the domain or IP (example.com). Host adds the port when one is present (example.com:8443). Origin is the scheme plus host (https://example.com:8443) and is what browsers use for the same-origin security check.

What port is used when a URL has no port number?

The scheme decides. https defaults to 443, http to 80, ssh to 22, and ftp to 21. This parser shows the effective port and marks it as the default, so you know which port a request would actually use.

Does the parser decode percent-encoded characters?

Yes, for query values. A parameter like name=John%20Doe is shown decoded as "John Doe" in the table. The raw search string is also shown untouched so you can compare the encoded and decoded forms.

Can I parse a URL without typing the https part?

Yes. If you paste a bare host or path such as example.com/pricing, the parser prepends https:// automatically and notes that it assumed the scheme. Paste a scheme explicitly, such as http:// or ftp://, to override that assumption.

Why does my URL fail to parse?

Usually the host is missing or malformed, the scheme is written incorrectly, or the string contains characters that are illegal in a URL and are not percent-encoded. Check for spaces, unescaped brackets, or a missing slash after the scheme.

Is it safe to paste URLs with tokens or session IDs?

Yes. Parsing runs entirely in your browser using its native URL engine. The link is never sent to a server, never logged, and never stored, so URLs containing access tokens, API keys, or internal hostnames stay on your device.

Comments

0 comments

0/2000 characters

No comments yet. Be the first to share your thoughts!