What a URL actually is
A URL (Uniform Resource Locator) is a structured string that tells a client exactly where a resource lives and how to reach it. It looks like a single line of text, but it is really a sequence of clearly delimited components, each separated by a reserved character — ://, @, :, /, ?, #. Once you learn those delimiters, you can read any URL like a sentence.
URLs are a subset of URIs (Uniform Resource Identifiers). Every URL is a URI, but a URI can also be a URN (a name without a location, like urn:isbn:0-486-27557-4). For day-to-day web work you almost always mean a URL, so that is what we focus on here. The authoritative specification is the WHATWG URL Standard, which is exactly what your browser implements — and exactly what the URL Parser uses to split a URL into its pieces.
A worked example
Consider this deliberately complete URL:
https://alice:s3cret@api.example.com:8080/v2/users?role=admin&active=true#results
Every component is present here. Broken down, it parses to:
| Component | Value |
|---|---|
| Scheme (protocol) | https: |
| Username | alice |
| Password | s3cret |
| Host | api.example.com:8080 |
| Hostname | api.example.com |
| Port | 8080 |
| Path | /v2/users |
| Query string | ?role=admin&active=true |
| Fragment | results |
Paste any URL of your own into the URL Parser and you will see exactly this breakdown, including a table of the individual query parameters.
The scheme (protocol)
The scheme is everything up to the first colon: https, http, ftp, mailto, file, ws. It tells the client which protocol to use to fetch the resource. In the WHATWG URL API the protocol property includes the trailing colon (https:), which trips up developers who expect just https. Remember to strip the colon if you only want the bare scheme.
Schemes are case-insensitive and always normalized to lowercase. HTTPS://Example.com and https://example.com are the same URL. “Special” schemes — http, https, ws, wss, ftp, and file — get extra parsing rules, such as default ports and mandatory // authority delimiters.
The authority: userinfo, host, and port
After :// comes the authority — the part that identifies who hosts the resource. It has up to three sub-parts:
Userinfo (alice:s3cret@) carries an optional username and password separated by a colon, terminated by @. Credentials in URLs are deprecated for security reasons — most browsers strip them or warn — but the parser still recognizes them.
Host is the domain or IP address. The host property includes the port when one is present (api.example.com:8080), while hostname is just the registrable part (api.example.com). This distinction is the single most common source of confusion, so it is worth committing to memory.
Port is the optional number after the colon. If you omit it, the scheme’s default applies — 80 for http, 443 for https, 21 for ftp. Because the default is implied, the WHATWG URL API returns an empty string for port when the URL uses the default port. So https://example.com reports port: "", not 443.
Path and path segments
The path (/v2/users) is everything from the first / after the authority up to the ? or #. It identifies the specific resource on the host. Splitting the path on / gives you its segments — ["v2", "users"] — which map cleanly to REST routes, file directories, or framework router patterns.
A leading slash is mandatory for special schemes. A trailing slash (/users/ vs /users) is technically a different path and some servers treat them differently, so be precise when debugging 404s. The URL Parser lists each segment separately so you can confirm exactly how many there are and whether an empty trailing segment exists.
Query string and parameters
The query string starts at ? and runs until #. It is a flat list of key=value pairs joined by &. Conventionally this carries non-hierarchical data: filters, search terms, pagination, tracking parameters.
The query string is not parsed by the URL standard into structured data automatically — it is just an opaque string. To read individual parameters, the browser exposes URLSearchParams, which handles decoding and repeated keys (?tag=a&tag=b yields both values). Watch for these pitfalls:
- Repeated keys are legal; a naive object will overwrite duplicates. Use
getAll()to keep them all. - Encoding matters: a literal
&or=inside a value must be percent-encoded, or the parser will mistake it for a delimiter. - Order is preserved by
URLSearchParamsbut is not semantically significant for most servers.
The fragment (hash)
The fragment is everything after # — results in our example. Two things make it special. First, it is never sent to the server: the browser strips it from the HTTP request and keeps it client-side. Second, it has two main uses — scrolling to an in-page anchor (#section-2) and driving client-side routing in single-page apps (#/dashboard). Because it never reaches the server, changing the fragment does not trigger a page reload.
Absolute vs relative URLs
An absolute URL contains a scheme and authority (https://example.com/page). A relative URL omits them and is resolved against a base — ../images/logo.png or /about. Resolution follows precise rules: a leading / replaces the whole path, .. walks up one segment, and a bare query (?q=1) keeps the current path. When you pass a relative URL plus a base to the URL() constructor, it returns the fully resolved absolute URL, which is the safest way to build links programmatically.
Encoding pitfalls
Most URL bugs are encoding bugs. Spaces, Unicode, and reserved characters must be percent-encoded — a space becomes %20, an é becomes %C3%A9. Use encodeURIComponent() for individual values, never on a full URL (it would mangle the :// and & delimiters). Internationalized domain names are converted to ASCII “punycode” (café.com → xn--caf-dma.com) so DNS can resolve them. The parser shows you the normalized, encoded form so you can see exactly what the browser will actually request.
Common debugging scenarios
- “My API call 404s but the URL looks right.” Check the path for a missing or extra trailing slash, and confirm the port is the one your server listens on.
- “My query parameter is empty server-side.” The value probably contained an unencoded
&or#, truncating it. Re-encode the value. - “Redirect drops part of my link.” The fragment was lost because it never traveled to the server — preserve it client-side.
- “Two URLs that should match don’t.” Case differences in the scheme/host are equivalent, but case in the path is significant; default ports may also be the culprit.
For a deeper history and design rationale behind each piece, read Anatomy of a URL. When you just need to see a specific URL split apart instantly, the in-browser URL Parser does the whole breakdown for you — nothing is uploaded, and the WHATWG-compliant output matches exactly what a browser would do.