Skip to content
FormatKit

Free online URL parser

URL parser that shows what it changed

This URL parser is free and needs no signup: paste an address and get the scheme, host, port, path, query parameters and fragment, each one decoded, using the browser’s own WHATWG URL implementation — so the values are exactly what fetch and the address bar would see. It resolves relative references against a base URL, reads a punycode host back into its readable form, and lists everything the parser normalised on the way in: the dropped default port, the lowercased host, the resolved ../ segments. When a URL will not parse, it says which character is the problem rather than reporting “invalid URL”.

  • 100% free
  • No signup
  • Native URL API
  • IDN and punycode
  • Relative resolution
Try:

Components

Every component of the parsed URL with its value and what it represents
hrefhttps://user:[email protected]:8443/eu/items;v=2/42?q=blue+suede+shoes&tag=sale&tag=new&utm_source=news#reviews
protocolhttps:
originhttps://shop.example.co.uk:8443
usernameuser
password••
hostshop.example.co.uk:8443
hostnameshop.example.co.uk
Unicode hostshop.example.co.uk
port8443
effective port8443
pathname/eu/items;v=2/42
search?q=blue+suede+shoes&tag=sale&tag=new&utm_source=news
hash#reviews

Path segments (3)

  1. 1. eu
  2. 2. items;v=2
  3. 3. 42

Query parameters (4)

Query parameters with the raw value as written and the decoded value
NameRawDecoded
qblue+suede+shoesblue suede shoes
tagsalesale
tagrepeatednewnew
utm_sourcenewsnews

What the parser changed on the way in

  • Dot segments were resolved, giving the path /eu/items;v=2/42.

Credentials are embedded in the URL. They are sent in an Authorization header, appear in browser history and Referer, and Chrome refuses them entirely on subresource requests.

A + appears in the query. URLSearchParams decodes it as a space, but the path and the fragment do not — there, + is literally a plus.

The fragment is never sent to the server. It exists only in the client, which is why analytics built on it needs JavaScript to report it.

A parameter name repeats. Nothing forbids it and getAll returns both, but frameworks disagree: PHP keeps the last, Rails needs a [] suffix to make an array, and Express keeps both.

Anatomy of a URL

Ten pieces, taken from https://user:[email protected]:8443/eu/items;v=2/42?q=shoes&tag=sale#reviews. RFC 3986 and the WHATWG URL Standard name the same pieces differently, and the JavaScript property is not always what the RFC calls it — which is why protocol keeps a colon and authority has no property at all.

URL components with an example value, the RFC 3986 term and the corresponding JavaScript URL property
PieceIn the exampleRFC 3986 termJavaScript property
Schemehttpsschemeurl.protocol — with the colon, as https:
Userinfouser:pw@userinfourl.username and url.password, separately
Hostshop.example.co.ukhosturl.hostname, always lowercase and in ASCII
Port:8443porturl.port — an empty string when it is the scheme default
Authorityuser:[email protected]:8443authorityNo single property; url.host is hostname plus port
Path/eu/items;v=2/42pathurl.pathname, always starting with / for http and https
Parameters;v=2path segment parameterNothing parses these — they are part of the segment text
Query?q=shoes&tag=salequeryurl.search with the ?, url.searchParams as a live object
Fragment#reviewsfragmenturl.hash with the #; never leaves the client
Originhttps://shop.example.co.uk:8443no equivalenturl.origin — the tuple same-origin policy compares

What has to be encoded, and where

RFC 3986 sorts every character into three buckets, and the bucket decides whether it can appear literally. The rule that trips people is that reserved characters are only a problem inside a value — a ? is structure in the URL and data in a parameter.

Character groups defined by RFC 3986 with the characters in each and the encoding rule that applies
GroupCharactersRule
UnreservedA-Z a-z 0-9 - . _ ~Safe everywhere and never need encoding. Encoding them anyway is legal but changes nothing.
Gen-delims: / ? # [ ] @Structural. Inside a value they must be encoded, or the parser reads them as the next component starting.
Sub-delims! $ & ' ( ) * + , ; =Reserved for the scheme to use. & = + and ; are the dangerous ones in a query value.
Space(a literal space)%20 everywhere. + means a space only when the reader is doing form decoding, which is the query and nowhere else.
Non-ASCIIé 東 emojiUTF-8 bytes, each percent-encoded: é becomes %C3%A9. In a host it becomes Punycode instead.
Percent%Always %25 when literal. A stray % that is not followed by two hex digits is what makes decodeURIComponent throw URIError.

How to parse a URL

Paste it, resolve it if it is relative, and see what the parser did to it.

  1. Paste the URL

    Put it in the top box, or press Ctrl+V anywhere on the page and a clipboard that starts with a scheme lands there. Six examples sit underneath — an internationalised host, an IPv6 literal, a mailto, a data URL and a relative reference — because each one exercises a different corner of the parser.

  2. Add a base if the reference is relative

    ../images/logo.svg has no meaning on its own, so put the document it appears in into the Base URL field and it resolves the way a browser would: the last path segment of the base is dropped, the dot segments are applied, and the result is an absolute URL. This is the same algorithm that turns a relative href in your HTML into a request.

  3. Read the components, the parameters, and the changes

    The first table gives all thirteen properties the URL API exposes, plus the effective port and the Unicode form of a punycode host. The second lists every query parameter with its raw and decoded value, marking repeats. The last panel is the interesting one: it names what the parser silently altered — a dropped default port, resolved dot segments, a lowercased host, stripped tab characters.

Technical specifications

ParserThe browser's built-in URL constructor, which implements the WHATWG URL Standard — the same code path fetch, the address bar and service workers use
Components shown13 properties: href, protocol, origin, username, password, host, hostname, port, pathname, search, hash, plus the effective port and the Unicode host
Query handlingEvery parameter listed with its raw and decoded value, + decoded as a space, repeated names marked, and empty values distinguished from missing ones
Internationalised domainsPunycode decoded with a full RFC 3492 implementation, so xn--mnchen-3ya.de reads back as münchen.de, and a mixed-script host raises a homograph warning
Relative resolutionAny reference against any base — absolute paths, ../ walks, bare queries and protocol-relative //host forms
Normalisation reportedTrimmed whitespace, stripped tabs and newlines, lowercased scheme and host, dropped default ports, resolved dot segments, backslashes converted to slashes
DiagnosticsFailures are named specifically — a space in the URL, a port above 65535, an unbracketed IPv6 address, a scheme with no authority, a relative reference with no base
Processing locationYour browser — the URL is parsed by the engine already running this page, so nothing is requested, resolved or logged

Frequently asked questions

Why is url.port empty for https://example.com:443/?

Because 443 is the default port for https, and the URL parser removes a default port during normalisation. The same happens to :80 on http and :21 on ftp. Read url.port and you get an empty string; read url.host and the port is absent there too. When you need a number to open a socket with, fall back to the scheme default yourself — the effective port row in the table above does exactly that.

Does the fragment reach the server?

No. Everything from the # onwards is stripped before the request goes out, which is why it is safe for client-side state and unsafe for anything you want in a server log. It is also why single-page routers built on hashes never showed up in analytics without JavaScript, and why an OAuth implicit flow that returns a token in the fragment keeps that token out of the server's access log — the one genuine security property of the design.

Is + a space in a URL?

Only in the query string, and only because form encoding says so. RFC 3986 treats + as an ordinary sub-delimiter with no special meaning anywhere, but HTML form submission defined application/x-www-form-urlencoded to write a space as +, and query strings inherited that convention. So URLSearchParams decodes q=blue+suede as blue suede, while the same + in a path segment stays a plus sign. If a value can legitimately contain a plus — a phone number, a base64 payload — encode it as %2B or watch it turn into a space.

Why did my host turn into xn--something?

Because DNS carries ASCII only, so an internationalised domain is converted to Punycode before the lookup. münchen.de becomes xn--mnchen-3ya.de, and that ASCII form is what appears in the DNS query, the TLS SNI extension and the Host header. The parser above shows both. It matters for more than curiosity: a certificate is issued for the ASCII name, log analysis has to match on it, and the gap between what a user reads and what resolves is exactly the space homograph attacks live in — which is why browsers display Punycode rather than Unicode when a label mixes scripts.

Why does new URL("/api/users") throw?

Because a URL without a scheme is a reference, not a URL, and the constructor refuses to guess. Pass a base as the second argument — new URL("/api/users", location.href) — and it resolves. The rules are worth knowing: a leading slash replaces the whole path, a leading ../ walks up from the base's directory, a bare query like ?page=2 keeps the path, and a protocol-relative //cdn.example.com/x inherits only the scheme. Use URL.canParse(input, base) when you want a boolean instead of a try/catch; it has been in every current browser since 2023.

How long can a URL be?

Longer than the 2,048 characters everyone quotes, but the limit that bites is not the browser's. Chrome and Firefox handle well over 100,000 characters in the address bar; Internet Explorer's 2,083 is where the number came from and it stopped mattering years ago. What breaks first is infrastructure: nginx allows 8 KB for the whole request line by default, Apache 8,190 bytes, and many CDNs, WAFs and analytics pipelines truncate or reject sooner. A 414 URI Too Long is the server telling you which one you hit.

Can two query parameters share a name?

Yes, and nothing in any specification forbids it — but the receiver decides what it means. searchParams.getAll("tag") returns both values in the browser; PHP keeps only the last unless the name ends in [], Rails needs the same suffix to build an array, Express and Go both give you a list, and ASP.NET joins them with a comma. That disagreement is a genuine source of bugs when a URL crosses stacks, so the parameter table above marks repeated names rather than quietly folding them together.

About parsing URLs

There are two specifications for what a URL is, and they disagree. RFC 3986 from 2005 defines a strict grammar in which many everyday strings are simply invalid. The WHATWG URL Standard, which browsers actually implement, defines a parsing algorithm that never fails on a string a user might type: it trims whitespace, deletes tab and newline characters wherever they occur, treats backslashes as slashes for http and https, lowercases the scheme and host, drops a default port and resolves dot segments. This page uses the browser’s implementation on purpose, because the values it reports are then the values your code will receive rather than a second opinion.

That gap between parsers is not academic. Server-side request forgery filters are routinely defeated by strings that two parsers read differently — https://expected.com\@evil.com/ is one host to a language that follows RFC 3986 and another to a browser that converts the backslash, and http://127.0.0.1#@example.com/ splits at a different point depending on whether the fragment is removed first. The lesson from a decade of these bugs is to parse once, with one implementation, and to pass around the parsed object rather than re-parsing a string at every layer. If you are checking whether a value is encoded correctly before it goes into a URL, the URL decoder works on the pieces this page hands you.

One property here deserves more attention than it gets: origin. Scheme, host and port together are the tuple the same-origin policy compares, so https://example.com and https://example.com:8443 are different origins, as are the http and https versions of the same host, while /a and /b on one host are not. Every CORS decision, cookie SameSite evaluation and postMessage check turns on that comparison, and a surprising number of “CORS is broken” afternoons end with a port or a scheme that did not match. When the response that follows carries a status you did not expect, the status code reference is the other half of the diagnosis.

Where the URL is parsed

In the tab you are reading. The URL never becomes a request: nothing is fetched, no DNS lookup happens, and the address is not logged or stored anywhere — which matters when the thing you are debugging is a signed link, a password-reset URL or a token in a query string.