Every time you click a link, type an address, or make an API call, you're working with URLs. Understanding how URLs are structured helps you build better web applications, debug issues faster, and work more effectively with APIs. This guide breaks down every component of a URL.
Key Takeaways
- 1URLs have up to 8 components: scheme, username, password, host, port, path, query, fragment
- 2Only the scheme and host are required—everything else is optional
- 3The query string (after ?) is sent to servers; the fragment (after #) stays in the browser
- 4Relative URLs inherit components from the current page URL
- 5Use the URL API in JavaScript for safe, reliable URL manipulation
The Complete URL Structure
Before diving into individual components, let's see the complete picture. This section shows a URL with all eight possible components and breaks down what each one does. Most URLs you encounter will only use a few of these, but understanding the full structure helps you work with any URL you encounter.
A URL can have up to eight distinct parts. Here's a fully-loaded example showing all of them:
https://user:pass@api.example.com:8080/v1/users?status=active&limit=10#resultsLet's break this down piece by piece. The table below maps each component to its value and purpose:
| Component | Value | Purpose |
|---|---|---|
| Scheme | https | Protocol for accessing the resource |
| Username | user | Authentication (rarely used) |
| Password | pass | Authentication (avoid in URLs!) |
| Host | api.example.com | Server address |
| Port | 8080 | Network port (optional) |
| Path | /v1/users | Resource location on server |
| Query | status=active&limit=10 | Additional parameters |
| Fragment | results | Page section (client-side only) |
Now let's examine each component in detail, starting with the scheme—the first thing browsers look at when processing a URL.
1. Scheme (Protocol)
The scheme determines how your browser communicates with the server. This section covers the most common schemes you'll encounter, from web protocols to special-purpose schemes like mailto and tel.
The scheme tells the browser (or client) how to access the resource. It appears at the beginning of the URL, followed by ://.
The table below lists common URL schemes. Most of your work will involve http and https, but understanding other schemes helps when building features like email links or WebSocket connections.
| Scheme | Use Case | Default Port |
|---|---|---|
https | Secure web pages and APIs (encrypted) | 443 |
http | Unencrypted web traffic (avoid for sensitive data) | 80 |
ftp | File transfers | 21 |
mailto | Email addresses (no //, just mailto:) | N/A |
tel | Phone numbers | N/A |
file | Local file system | N/A |
ws / wss | WebSocket connections (real-time) | 80 / 443 |
data | Inline data (images, etc.) | N/A |
// Access the scheme in JavaScript
const url = new URL('https://example.com/page');
console.log(url.protocol); // "https:" (includes the colon)
// Change the scheme
url.protocol = 'http';
console.log(url.href); // "http://example.com/page"The code above shows how to read and modify the scheme using JavaScript's URL API. Notice that protocol includes the colon—this is intentional and matches the URL specification. When changing protocols, you can omit the colon and the API will add it.
Always use HTTPS
HTTPS encrypts data in transit, preventing eavesdropping and man-in-the-middle attacks. Modern browsers mark HTTP sites as "Not Secure." There's no good reason to use HTTP for production websites.
With the scheme covered, let's move to the host—the address that tells your browser which server to contact.
2. Host (Domain Name)
The host is arguably the most important part of a URL—it's the server address. This section explains domain structure, subdomains, and special host values like localhost and IP addresses.
The host identifies which server to contact. It can be a domain name, subdomain, or IP address.
Domain Name Structure
https://docs.api.example.comDomain names are read right-to-left:
| Part | Name | Example |
|---|---|---|
| Top-Level Domain (TLD) | The rightmost part | .com |
| Second-Level Domain | The main domain name | example |
| Subdomain(s) | Optional prefixes | docs.api |
Special Host Values
# Localhost - your own computer
http://localhost:3000
http://127.0.0.1:3000
http://[::1]:3000 # IPv6 localhost
# IPv4 address
http://192.168.1.1
# IPv6 address (must be in brackets)
http://[2001:db8::1]:8080
# Wildcard subdomain (in server config, not URLs)
*.example.comThese special host values are essential for development. Localhost (127.0.0.1) refers to your own machine, while IPv6 addresses require brackets to distinguish colons in the address from the port separator.
const url = new URL('https://docs.api.example.com:8080/path');
// Access different parts of the host
console.log(url.host); // "docs.api.example.com:8080" (includes port)
console.log(url.hostname); // "docs.api.example.com" (without port)
console.log(url.port); // "8080"
// Change the host
url.hostname = 'new.example.com';Note the distinction between host (includes port) and hostname (domain only). This is a common source of confusion—use hostname when you want just the domain, and host when you need the full authority including the port number.
3. Port
Ports act like apartment numbers for servers—they direct traffic to specific services running on the same machine. This section explains default ports and when you need to specify them explicitly.
The port specifies which network port to connect to on the server. If omitted, the default port for the scheme is used.
The table below shows default ports for common schemes. You only need to include the port in URLs when using non-standard ports.
| Scheme | Default Port | When to Specify |
|---|---|---|
| https | 443 | Only if using a non-standard port |
| http | 80 | Only if using a non-standard port |
| ftp | 21 | Only if using a non-standard port |
# Default ports are implied
https://example.com → https://example.com:443
http://example.com → http://example.com:80
# Non-standard ports must be explicit
https://example.com:8443
http://localhost:3000
http://api.local:5000These examples show how default ports are implied—you never see :443 on HTTPS URLs in practice because it's assumed. Explicitly including default ports isn't wrong, but it's unconventional and makes URLs longer than necessary.
In development, you'll often see non-standard ports like :3000, :8080, or :5000 because multiple services run on the same machine.
After connecting to the right port, the browser needs to know which resource to request. That's where the path comes in.
4. Path
The path identifies what you want from the server—a file, an API endpoint, or a page. This section covers path structure, common patterns for REST APIs, and the sometimes-significant trailing slash.
The path identifies a specific resource on the server. It starts with / and can contain multiple segments separated by /.
https://example.com/api/v2/users/123/posts| Segment | Purpose |
|---|---|
/api | Namespace for API endpoints |
/v2 | API version |
/users | Resource type |
/123 | Specific resource ID |
/posts | Related sub-resource |
Common Path Patterns
# Static files
/images/logo.png
/css/styles.css
/js/app.bundle.js
# RESTful resources
/users → List all users
/users/123 → Get user 123
/users/123/posts → Get posts by user 123
# Versioned APIs
/api/v1/products
/api/v2/products
# Hierarchical content
/blog/2026/01/my-post
/docs/getting-started/installation
# Single-page app routes
/dashboard
/settings/profile
/orders/historyThese patterns show how paths create hierarchical structures. Static file paths map directly to files on disk, while REST API paths represent resources and relationships. SPA routes exist only in JavaScript—the server sees the same path regardless of which "page" the user is on.
const url = new URL('https://example.com/api/v2/users/123');
console.log(url.pathname); // "/api/v2/users/123"
// Split into segments
const segments = url.pathname.split('/').filter(Boolean);
// ["api", "v2", "users", "123"]
// Modify the path
url.pathname = '/api/v3/customers';
console.log(url.href); // "https://example.com/api/v3/customers"The code shows how to parse paths into segments using split() and filter(Boolean). The filter removes empty strings caused by leading and trailing slashes. This pattern is useful when routing or extracting resource IDs from URLs.
Trailing Slash Matters
Whether a path ends with / can affect how servers handle the request:
# These might be treated differently by servers
/products → Often serves a resource or redirects to /products/
/products/ → Often serves an index/listing
# Best practice: Be consistent
# Pick one convention and redirect the otherThe trailing slash issue causes real problems: duplicate content for SEO, broken relative URLs, and inconsistent caching. The best solution is to pick one convention (with or without trailing slashes) and configure your server to redirect the other.
Paths identify resources, but often you need to pass additional data. That's what query strings are for.
5. Query String
Query strings carry data that modifies how the server handles your request—filters, pagination, search terms, and tracking parameters. This section introduces the basics; for a deep dive, see our complete Query Parameters guide.
The query string starts with ? and contains key-value pairs separated by &. It passes additional data to the server.
https://shop.example.com/products?category=electronics&sort=price&order=asc&page=2For a complete guide to query strings, see Query Parameters.
const url = new URL('https://example.com/search?q=laptop&page=2');
// Access query string
console.log(url.search); // "?q=laptop&page=2"
console.log(url.searchParams); // URLSearchParams object
// Get individual parameters
const query = url.searchParams.get('q'); // "laptop"
const page = url.searchParams.get('page'); // "2"
// Add/modify parameters
url.searchParams.set('sort', 'price');
url.searchParams.delete('page');
console.log(url.href);
// "https://example.com/search?q=laptop&sort=price"The code demonstrates the power of URLSearchParams: you can get, set, and delete parameters without worrying about encoding or string manipulation. The search property gives you the raw query string, while searchParams provides the parsed object interface.
Query strings are sent to the server, but there's another component that stays entirely in the browser: the fragment.
6. Fragment (Hash)
The fragment (or hash) is unique among URL components—it never leaves the browser. This section explains how fragments work, their use cases from page navigation to SPA routing, and the critical distinction between fragments and query strings.
The fragment starts with # and identifies a section within the page. Unlike the query string, the fragment is never sent to the server—it's handled entirely by the browser.
https://example.com/docs/api#authenticationFragment Use Cases
| Use Case | Example | How It Works |
|---|---|---|
| Page sections | #introduction | Scrolls to element with id="introduction" |
| SPA routing | /#/dashboard | Client-side router reads the hash |
| Tab selection | #tab=settings | JavaScript reads and activates tab |
| Deep linking | #t=1m30s | YouTube uses this for video timestamps |
const url = new URL('https://example.com/page#section-3');
console.log(url.hash); // "#section-3"
// Change the fragment
url.hash = 'section-5';
console.log(url.href); // "https://example.com/page#section-5"
// Remove the fragment
url.hash = '';
// Listen for hash changes
window.addEventListener('hashchange', (event) => {
console.log('Old URL:', event.oldURL);
console.log('New URL:', event.newURL);
});The hashchange event is particularly useful for SPAs that use hash-based routing. Unlike regular navigation, changing only the fragment doesn't reload the page, making it perfect for client-side state that should be bookmarkable but doesn't require server involvement.
Fragment vs Query String
Query string: Sent to the server, used for server-side processing (search, filters, pagination).
Fragment: Stays in browser, used for client-side navigation (scroll position, SPA routes, tabs).
There's one more component we haven't covered—credentials. While rarely used today, you may encounter them in legacy systems.
7. Username and Password (Avoid These)
URLs technically support embedded credentials, but this feature is deprecated for good reason. This section explains why you should never use URL credentials and what to do if you encounter them in existing code.
URLs can include credentials in the format username:password@host. This is a legacy feature and should be avoided.
ftp://admin:secret123@ftp.example.com/filesSecurity Warning
Never embed passwords in URLs. They appear in browser history, server logs, referrer headers, and can be shared accidentally. Modern browsers warn users and may block these URLs.
const url = new URL('https://user:pass@example.com/');
console.log(url.username); // "user"
console.log(url.password); // "pass"
// These are automatically hidden from href in some contexts
// but still present in the URL objectIf you need to work with URLs containing credentials (perhaps migrating legacy code), the URL API provides access through username and password properties. Always strip these before logging or displaying URLs to prevent credential exposure.
Now that we've covered individual components, let's look at how URLs relate to each other through relative references.
Relative vs Absolute URLs
Not every URL needs all components. Relative URLs let you reference resources based on the current location, making code more portable and links shorter. This section covers both types and how browsers resolve relative references.
Absolute URLs include the full path from scheme to resource. Relative URLs are resolved against a base URL.
# Absolute URLs - complete address
https://example.com/products/shoes
https://api.example.com/v1/users
# Relative URLs - resolved from current page
./sibling-page → Same directory
../parent/page → Parent directory
/absolute/path → From root
//other.com/path → Same scheme, different host
?query=new → Same page, different query
#section → Same page, different fragmentHow Relative URLs Resolve
Given a current page of https://example.com/blog/posts/my-article:
The table below shows how each type of relative URL resolves against this base. Understanding these patterns helps you write cleaner links and debug routing issues.
| Relative URL | Resolves To |
|---|---|
other-article | https://example.com/blog/posts/other-article |
./images/photo.jpg | https://example.com/blog/posts/images/photo.jpg |
../about | https://example.com/blog/about |
/contact | https://example.com/contact |
//cdn.example.com/img.png | https://cdn.example.com/img.png |
?page=2 | https://example.com/blog/posts/my-article?page=2 |
#comments | https://example.com/blog/posts/my-article#comments |
// Resolve relative URLs in JavaScript
const base = 'https://example.com/blog/posts/article';
new URL('./next', base).href;
// "https://example.com/blog/posts/next"
new URL('../about', base).href;
// "https://example.com/blog/about"
new URL('/contact', base).href;
// "https://example.com/contact"
new URL('?page=2', base).href;
// "https://example.com/blog/posts/article?page=2"The URL constructor's second parameter (the base URL) is key to this resolution. When you pass a relative URL as the first argument and an absolute URL as the second, the constructor resolves the relative URL against the base—exactly how browsers handle links in HTML.
Beyond standard HTTP URLs, you'll encounter several special URL types in modern web development.
Special URL Types
Modern web development uses several URL formats beyond standard HTTP addresses. This section covers data URLs, blob URLs, and mailto URLs—understanding these helps when working with file uploads, downloads, and email integration.
Data URLs
Data URLs embed the content directly in the URL, useful for small images or inline resources:
data:text/html,<h1>Hello World</h1>
data:text/plain;base64,SGVsbG8gV29ybGQ=
data:image/png;base64,iVBORw0KGgo...Data URLs follow the pattern data:[mediatype][;base64],data. They're useful for embedding small assets to reduce HTTP requests, but large data URLs can bloat HTML and aren't cached like separate files.
Blob URLs
Blob URLs reference binary data in memory:
// Create a blob URL
const blob = new Blob(['Hello World'], { type: 'text/plain' });
const blobUrl = URL.createObjectURL(blob);
// "blob:https://example.com/550e8400-e29b-41d4-a716-446655440000"
// Use it (e.g., for downloads or media)
const link = document.createElement('a');
link.href = blobUrl;
link.download = 'hello.txt';
// Clean up when done
URL.revokeObjectURL(blobUrl);Blob URLs are created with URL.createObjectURL() and point to in-memory data. They're commonly used for file previews and downloads without server round-trips. Always call revokeObjectURL() when done to free memory—blob URLs don't garbage collect automatically.
Mailto URLs
# Basic mailto
mailto:hello@example.com
# With subject and body
mailto:hello@example.com?subject=Hello&body=Hi%20there
# Multiple recipients
mailto:one@example.com,two@example.com?cc=three@example.comMailto URLs trigger the user's email client. The query parameters (subject, body, cc, bcc) pre-fill the email composition window. Remember to URL-encode the body parameter if it contains special characters.
Let's wrap up with a comprehensive look at JavaScript's URL API and how to use it effectively.
Working with URLs in JavaScript
The URL API is your Swiss Army knife for URL manipulation. This section provides a comprehensive reference showing all available properties and methods, plus practical patterns for validation and construction.
The URL class provides the safest way to parse, modify, and build URLs:
// Parse a URL
const url = new URL('https://example.com:8080/path?q=test#section');
// All available properties
console.log(url.href); // Full URL
console.log(url.origin); // "https://example.com:8080"
console.log(url.protocol); // "https:"
console.log(url.host); // "example.com:8080"
console.log(url.hostname); // "example.com"
console.log(url.port); // "8080"
console.log(url.pathname); // "/path"
console.log(url.search); // "?q=test"
console.log(url.searchParams);// URLSearchParams object
console.log(url.hash); // "#section"
// Build a URL from scratch
const newUrl = new URL('https://api.example.com');
newUrl.pathname = '/v1/users';
newUrl.searchParams.set('status', 'active');
newUrl.searchParams.set('limit', '10');
console.log(newUrl.href);
// "https://api.example.com/v1/users?status=active&limit=10"
// Validate URL format
function isValidUrl(string) {
try {
new URL(string);
return true;
} catch {
return false;
}
}
isValidUrl('https://example.com'); // true
isValidUrl('not a url'); // falseThis comprehensive example shows all URL properties and the validation pattern using try-catch. The origin property (scheme + host + port) is particularly useful for same-origin security checks. The validation function leverages the fact that new URL() throws on invalid input.
Even with these tools, URL handling can go wrong. Let's look at common issues and how to solve them.
Common URL Issues
This section covers the three most common URL bugs: encoding problems, double encoding, and trailing slash inconsistency. Recognizing these patterns helps you debug faster.
Encoding Problems
// Problem: Special characters break URLs
const broken = 'https://example.com/search?q=Tom & Jerry';
// The & is interpreted as parameter separator!
// Solution: Always use URL API or encode properly
const url = new URL('https://example.com/search');
url.searchParams.set('q', 'Tom & Jerry');
console.log(url.href);
// "https://example.com/search?q=Tom+%26+Jerry"The fix is simple: always use the URL API or URLSearchParams instead of string concatenation. These APIs handle encoding automatically, preventing the ampersand from being interpreted as a parameter separator.
Double Encoding
// Problem: Encoding an already-encoded URL
const encoded = 'https://example.com/search?q=hello%20world';
const doubleEncoded = encodeURIComponent(encoded);
// "https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello%2520world"
// %20 became %2520 - broken!
// Solution: Only encode raw values, not complete URLs
const query = 'hello world';
const url = 'https://example.com/search?q=' + encodeURIComponent(query);The telltale sign of double encoding is %25 in your URL (the percent sign itself got encoded). If you see %2520 instead of %20, something encoded an already-encoded space. The solution: only encode raw values, never URLs that are already encoded.
Trailing Slash Inconsistency
// These might give different results
fetch('/api/users') // Might work
fetch('/api/users/') // Might 404 or give different data
// Solution: Be consistent, handle on server with redirects
// Or normalize URLs before making requestsThe safest approach is to normalize URLs in your code before making requests—either always include trailing slashes or never include them. Pick one convention and stick to it across your entire application.