You paste URLs every day, but a URL is really seven tiny fields in a trench coat: scheme, userinfo, host, port, path, query, fragment. Knowing which is which turns debugging from superstition into procedure — you'll know why the port disappeared, why %20 shows up in your analytics, and why that # section never shows up in server logs.
Paste any link — get every component plus decoded query parameters, editable, with a rebuilt URL.
Open the URL Parserhttps://user:[email protected]:8443/products/search?q=bag&max=150#results
| Part | Example | What it does |
|---|---|---|
| Scheme | https | Picks the protocol — and its default port (443 for https, 80 for http) |
| Userinfo | user:pass@ | Legacy credentials-in-URL. Rare, still valid, frowned upon |
| Host | store.example.com | Where to connect — DNS name or IP |
| Port | :8443 | Overrides the scheme's default port |
| Path | /products/search | The resource on that server; starts with / |
| Query | ?q=bag&max=150 | &-separated key=value pairs; the only place ? and & are structural |
| Fragment | #results | In-page anchor; browser-only, never sent to the server |
Type https://example.com:443/ into the URL parser and the port field comes back empty. The WHATWG URL parser normalizes away any port that equals the scheme's default — 443 for https, 80 for http, 21 for ftp — because https://example.com:443/ and https://example.com/ are the same resource. A port like :8443 or :3000 (dev servers love 3000) is non-default, so it survives parsing and shows up everywhere: in the origin, in CORS checks, in your cookie domain matching. That's also why localhost:3000 and localhost can't share login cookies by default — different origins.
URLs were born ASCII-only, so everything else gets escaped: a percent sign followed by two hex digits per byte. The ones you'll actually meet:
%20 — space. In query strings, + also means space (form-encoding legacy); decoders treat both the same.%25 — a literal %. This is why "10% off" becomes 10%25%20off.%26, %3D — & and =, escaped when they're data instead of structure.%C3%A9 — é: two percent-escapes because é is two bytes of UTF-8. Japanese and emoji run three and four.The classic bug: hand-building a query string with raw values. A value like P&G silently splits into two params. The fix is one line — encodeURIComponent(value) — and it's why the URL parser tool rebuilds URLs for you instead of trusting string concatenation.
Everything between ? and # is the query string. Rules worth internalizing:
tag=a&tag=b is a list, and servers that expect arrays depend on it.?flag&x=1 — flag has no = at all; it's a boolean-ish flag.q=laptop+bag reads "laptop bag". If you see %2B, that's an escaped real plus sign.Everything after # stays in the browser. The browser scrolls to the matching element ID (or lets JavaScript read it via location.hash), strips it from the request, and logs nothing. Consequences you can verify: adding #anything to a URL never triggers a reload or a new server hit, analytics tools deliberately put campaign IDs after ? rather than # when they want the server (or a redirect) to see them, and single-page apps route with fragments precisely because fragment changes are free.
A URI identifies; a URL identifies and locates. https://example.com/x is both — it names the resource and says how to fetch it. mailto:[email protected] and urn:isbn:0451450523 are URIs that aren't URLs: they identify, but there's no fetch procedure. Web people say "URL" for everything because in browser-land, everything worth parsing is one; the WHATWG spec governs that parser, and it's the exact code this site's URL parser calls.
Scheme, userinfo, host, port, path, query string, and fragment. In https://user:[email protected]:8443/a/b?x=1#top those are https, user:pass@, example.com, 8443, /a/b, ?x=1, and #top. Only scheme, host, and path are required in practice; the rest appear when used.
Hostname is the domain alone (example.com). Host is hostname plus port when one is written (example.com:8443). HTTP's Host header technically sends the host — domain and explicit port both — which is how one server can serve multiple sites on different ports.
URLs allow a limited ASCII set, so everything else gets percent-encoded: a % followed by the character's hex bytes. A space becomes %20, a literal % becomes %25 (which is why 10% off encodes as 10%25%20off), and non-ASCII characters encode per UTF-8 byte — é becomes %C3%A9 because é is two bytes.
Never. The fragment is client-side only: the browser keeps it, jumps to the matching element, and strips it before the HTTP request. That's why server logs never contain fragments and why single-page apps use them for navigation — changing #anything never triggers a reload.