Network Tools

URL Parser

Parse URL components and edit query parameters.

🔗

Enter URL to start parsing

What is URL Parser?

URL parser breaks down a URL into its component parts for analysis and manipulation.

URL Components

  • Protocol: http, https
  • Host: Domain name
  • Port: Network port
  • Path: Resource path
  • Query: Parameters
  • Hash: Fragment identifier

Use Cases

  • URL debugging
  • Link analysis
  • Parameter extraction

Decomposes one absolute URL with the browser's WHATWG parser and prints every component separately: scheme, userinfo, host against hostname, port, path, query and fragment, plus a per-pair query table you can edit. It is built for debugging redirect and callback URLs, tracking parameters and API endpoints whose encoded characters hide what the server actually receives.

Which parser actually runs

Input goes straight into the constructor new URL(input), which implements the URL Living Standard rather than the older url.parse or a hand-rolled RFC 3986 splitter. Normalisation therefore happens before anything is shown: scheme and hostname are lowercased, so HTTPS://Shop.Example.COM is reported as https: and shop.example.com; a port equal to the scheme default — 80 for http and ws, 443 for https and wss, 21 for ftp — is erased outright, leaving port empty and no colon in href; . and .. segments are resolved against the path; an empty path becomes /; in special schemes a backslash counts as a slash. Internationalised hosts come back as punycode labels starting xn--, while percent triplets already inside the path or query keep their original spelling, lower-case hex included.

host, hostname, port and origin are four different strings

hostname is the bare name. host appends :port only when the port is non-default, so the two are identical for most production URLs — code that compares host alone silently misses https://example.com:8443/. origin is scheme plus host with userinfo, path, query and fragment stripped; it is the value browsers compare for CORS and storage partitioning, and for non-special schemes such as foo://example.com/x or data:text/plain,hi it is the literal string null, which matches no allow-list entry. Userinfo is returned as stored, still percent-encoded — a space stays %20, an at sign stays %40 — so copy the password field and decode it separately. Credentials in URLs are deprecated by RFC 9110 and are stripped by browsers and proxies anyway.

Why a valid URL can still be rejected

Parameter rows come from URLSearchParams, which has already decoded each value and turned + into a space. The panel then calls decodeURIComponent on that result to fill its decoded line, so the value is percent-decoded twice. The second pass throws URIError: URI malformed the moment a decoded value contains a lone percent sign — precisely what ?discount=100%25 produces: the parser yields 100%, the extra call fails, and the surrounding catch reports the same text used for an unparsable URL. The link is fine; only that per-row decode is broken. Genuine double encoding survives by design, since ?q=a%2520b gives the value a%20b and the decoded line a b.

Worked example

A redirect URL with credentials, a default port and a dot-dot segment

Input
HTTPS://User:pa%[email protected]:443/a/../b?q=%E4%B8%AD+ok&ref=#top
Output
href: https://User:pa%[email protected]/b?q=%E4%B8%AD+ok&ref=#top
protocol: https:
username: User
password: pa%40ss
host: shop.example.com
hostname: shop.example.com
port: (row hidden)
pathname: /b
search: ?q=%E4%B8%AD+ok&ref=
hash: #top
origin: https://shop.example.com
q = 中 ok
ref = (empty string)

Nothing here is guessed: :443 equals the https default, so the port row is hidden and origin carries neither port nor userinfo, while :8443 would have survived. /a/../b collapses to /b and the host drops to lower case. q reads 中 ok because %E4%B8%AD decodes to the character and + becomes a space, whereas the password keeps %40 in place of the at sign.

Limits and pitfalls

  • The constructor receives no base URL, so a relative reference such as example.com/a or /a?b=1 is rejected with the same message as a malformed link; prepend a scheme to parse it.
  • Rows are keyed by parameter name, so a query carrying one name twice collides in the list, and searchParams.set keeps a single pair while deleting removes every occurrence. Give repeated values distinct names such as tag[] or tag.1 if you need to edit them one by one.
  • Every edit is written back through URL.toString(), so the box changes beyond your action: a bare host gains a trailing /, an empty ? or # is dropped, and existing percent triplets stay exactly as typed instead of being re-encoded.
  • A value that legitimately contains a percent sign cannot be displayed: ?discount=100%25 parses, but the extra decode step fails and the tool reports an invalid URL. Use another sample when the literal character is the point.
  • The fragment is displayed but never sent to a server, and userinfo stays percent-encoded on screen, so neither string tells you what an HTTP request or its credentials block really contains.

Related tools

When it goes wrong

Every keystroke while I type shows a red invalid-URL banner, then it disappears at the end

CauseThe box is watched, not submitted: watch(urlInput) calls new URL() on every change, so h, http:/ and bare https:// all throw, and the throw is mapped to the same message used for a malformed link.

FixPaste the whole URL in one action. If you type it by hand, the banner clears as soon as scheme and host are complete — https://e already parses — so treat it as an unfinished-input indicator and do not investigate until the address is whole.

I clicked Add before pasting anything and got a link to example.com with my parameter attached

CauseaddParam falls back to new URL("https://example.com") when the box is empty, then appends. The Add button is only disabled while the key field is blank — it never checks the URL field.

FixPaste the target URL first, then add. To undo the fabricated link, use Clear rather than the ✕ on the row: removing the pair leaves https://example.com/ behind, which the parser happily accepts.

The copied report lost the hostname, port and hash lines that are visible on screen

CausecopyResult writes a part only when it is non-empty, and skips hostname entirely when it equals host. It also prints parameter values in their decoded form, without the decoded line. The cards below render hostname and origin unconditionally, so the two views legitimately differ.

FixUse the per-field copy buttons when an exact string matters, and read hostname, port and origin straight off the card. Treat the copied text as a summary, not a mirror of the panel.

I typed a space and a + into a value, and the link came back containing both + and %2B

CauseEdits are serialised by URLSearchParams under application/x-www-form-urlencoded rules: a space leaves as +, a typed + as %2B, & and = as %26 and %3D, and non-ASCII as UTF-8 triplets such as %E4%B8%AD. The value box keeps showing the decoded text, so the box and the URL never match character for character.

FixType the value as the server should receive it, then read the search field to see the wire form. If the consumer decodes raw instead of as a form, a space must be %20 — impossible to force here, so build that pair in URL Encode and paste the finished URL back.

The hostname row for an IPv6 link reads [::1], brackets included

CauseThe WHATWG serialiser keeps the brackets as part of the host: for http://[::1]:8080/x the components are hostname = [::1] and host = [::1]:8080. Those brackets are URL syntax, not part of the address, and no trimming happens before display.

FixStrip the leading [ and trailing ] before passing the string to anything that expects a bare IP — ping, a socket library, or an IP lookup page. Keep them when you rebuild the URL, or the colon inside the address will be read as a port separator.

Editing one link by hand versus six other routes to the same answer

What you are doingThis URL ParserWhat to use instead
Change one query value and get a usable link backThe parameter table is writable. Editing a value calls searchParams.set, the ✕ button calls searchParams.delete, and the result is written back through URL.toString() into the input box, so that box becomes the new link and can be copied as is.A five-line Node script with new URL(process.argv[1]), u.searchParams.set() and console.log(u.href), run as node rewrite.mjs "$URL". Choose the script when the same edit repeats on every build or every log line; choose this page when a person is deciding what the new value should be.
The string is a relative reference: /a?b=1 or example.com/aThe constructor receives one argument and no base, so both strings throw and the banner reports an invalid URL — the exact message a genuinely broken link gets. Nothing is inferred and no scheme is supplied.Pass the base yourself: new URL("/a?b=1", "https://example.com") in the console, or your router's own route matcher. Do it whenever the base is known for certain; typing https:// in front here would produce a plausible link with an origin you invented.
One percent-encoded string from a log field or a request body has to become readableDecoding is per query pair only. Each stored value is already decoded by URLSearchParams, and the extra decodeURIComponent call is what fills the "decoded" line — which is rendered only when it differs from the stored value. Path segments and body text are never decoded at all.URL Encode in decode mode: whole-string percent decoding with an explicit mode switch. Use it for any string that is not a complete absolute URL, and keep this page for the case where you need the parts separated.
You need the Unicode form of an internationalised host, or the punycode of a Unicode hostThe parser has already converted the host: مثال.إختبار comes back as xn--mgbh0fb.xn--kgbechtv. That is one direction only and there is no way to go back inside this page.Punycode tool converts label by label in both directions. Reach for it when the question is "what is this domain in the other form"; stay here when the question is "what is inside this URL".
Two hundred URLs from an access log need the same breakdownOne paste per URL, parsed on every keystroke, with no batch field and no history. The page is built for confirming a rule on a single sample.A shell loop: awk "{print $7}" access.log | xargs -n1 node -e "console.log(new URL(process.argv[1]).origin)". Confirm the rule here on one line first, then let the loop do the other 199.
Compare two links component by component — a redirect chain, or A/B variantsOne URL at a time. A second paste replaces the first, so you end up reading two states from memory.Normalise both through the same Node one-liner, print each component on its own line, then feed the two blocks to Text Diff. Character-level diff names the difference to the character — "#2 differs only in sort" — which switching between two single-URL panels cannot show at all.

Frequently Asked Questions

What are the parts of a URL?
The full structure is scheme://userinfo@host:port/path?query#hash. The common confusions: the query starts at ? and ends before #; the fragment after # is never sent to the server; an omitted port falls back to the protocol default (80 for http, 443 for https). This tool breaks every part out and parses the query into a key-value table.
Why do parsed parameter values gain or lose characters?
Almost always an encoding issue: a value containing & or = was not encoded and got split into two parameters, or it was encoded twice (%2520). Encode each value individually with encodeURIComponent when building, and decode each value individually when parsing — never process the whole URL in one pass.
Can the server see the hash (#fragment)?
No. The fragment lives only in the browser and is never sent with requests. SPA routes like #/page/1 are handled entirely client-side — if server logs never show some "pages", check whether they use hash routing.

Content reviewed: