Working with URLs in JavaScript used to mean wrestling with regex patterns and string manipulation. Extracting the hostname? Regex. Getting query parameters? Split on & and =. Modifying a single parameter? Rebuild the entire URL by hand.
Key Takeaways
- 1The URL constructor parses any URL into a structured object with properties like hostname, pathname, and searchParams
- 2URLSearchParams handles query string manipulation with automatic encoding/decoding
- 3Both APIs are built into browsers and Node.js—no libraries needed
- 4URL objects are mutable: change properties and read back the updated href
- 5Use try/catch when parsing user input since invalid URLs throw errors
The modern URL and URLSearchParams APIs change everything. They parse URLs into structured objects, handle encoding automatically, and make manipulation straightforward. No regex required. This guide shows you how to use them effectively for real-world tasks.
Parsing a URL
The URL constructor is your entry point for all URL manipulation. Pass any valid URL string, and it breaks it down into parts instantly. This is the modern replacement for regex-based URL parsing.
https://example.com:8080/path/page?name=john&age=30#sectionconst url = new URL('https://example.com:8080/path/page?name=john&age=30#section');
// Access any part of the URL directly
url.protocol; // "https:"
url.hostname; // "example.com"
url.port; // "8080"
url.pathname; // "/path/page"
url.search; // "?name=john&age=30"
url.hash; // "#section"
// The full URL is always available
url.href; // "https://example.com:8080/path/page?name=john&age=30#section"The URL object validates the input and throws an error for malformed URLs. This is actually helpful—you find out immediately if something is wrong rather than dealing with subtle bugs later.
// Invalid URLs throw errors - catch them to handle gracefully
try {
const url = new URL('not-a-valid-url');
} catch (error) {
console.log('Invalid URL:', error.message);
// "Invalid URL: Invalid URL"
}
// Tip: Create a safe parsing function
function parseUrl(string) {
try {
return new URL(string);
} catch {
return null;
}
}
const url = parseUrl(userInput);
if (url) {
// Safe to use
}All URL Properties
The URL object gives you access to every part of a URL through properties. The table below shows the complete list with examples. Pay attention to subtle differences like host vs hostname.
| Property | Returns | Example |
|---|---|---|
href | The complete URL | https://example.com:8080/page?q=1#top |
origin | Protocol + hostname + port | https://example.com:8080 |
protocol | Protocol with trailing colon | https: |
host | Hostname + port (if non-default) | example.com:8080 |
hostname | Just the hostname | example.com |
port | Port number (empty string if default) | 8080 |
pathname | Path starting with / | /page |
search | Query string including ? | ?q=1 |
searchParams | URLSearchParams object | See below |
hash | Fragment including # | #top |
Note the subtle differences: host includes the port, hostname doesn't. origin is read-only, but most other properties can be modified (see "Building URLs" below).
Working with Query Parameters
Query parameters are the key-value pairs after the ? in a URL. They're used everywhere: search queries, filters, pagination, tracking codes, OAuth callbacks. The URLSearchParams API makes working with them effortless.
Every URL object has a searchParams property that returns a URLSearchParams object. This object provides methods for reading, adding, modifying, and deleting parameters—with automatic encoding handled for you.
Reading Parameters
const url = new URL('https://example.com/search?q=hello&page=2&tag=js&tag=web');
// Get a single value (returns first match if multiple exist)
url.searchParams.get('q'); // "hello"
url.searchParams.get('page'); // "2" (always a string!)
url.searchParams.get('missing'); // null (not undefined)
// Get all values for a key (useful when parameter appears multiple times)
url.searchParams.getAll('tag'); // ["js", "web"]
// Check if a parameter exists
url.searchParams.has('q'); // true
url.searchParams.has('missing'); // false
// Get the number of parameters (counting duplicates)
url.searchParams.size; // 4
// Loop through all parameters
for (const [key, value] of url.searchParams) {
console.log(`${key}: ${value}`);
}
// Output:
// q: hello
// page: 2
// tag: js
// tag: webImportant: Parameter values are always strings. If you need a number, parse it: parseInt(url.searchParams.get('page'), 10). For booleans, compare against the string: url.searchParams.get('active') === 'true'.
Adding and Changing Parameters
Modifications to searchParams automatically update the URL object. The encoding is handled for you—just pass in the raw values.
const url = new URL('https://example.com/search');
// set() adds a parameter, or replaces it if it exists
url.searchParams.set('q', 'javascript');
url.searchParams.set('page', '1');
console.log(url.href);
// "https://example.com/search?q=javascript&page=1"
// append() adds a parameter, allowing duplicates
url.searchParams.append('tag', 'tutorial');
url.searchParams.append('tag', 'beginner');
console.log(url.href);
// "https://example.com/search?q=javascript&page=1&tag=tutorial&tag=beginner"
// delete() removes a parameter (all values if duplicates exist)
url.searchParams.delete('page');
console.log(url.href);
// "https://example.com/search?q=javascript&tag=tutorial&tag=beginner"
// Special characters are encoded automatically
url.searchParams.set('query', 'Tom & Jerry');
console.log(url.href);
// "https://example.com/search?q=javascript&tag=tutorial&tag=beginner&query=Tom+%26+Jerry"Sorting Parameters
Sometimes you need parameters in a consistent order—for caching, debugging, or comparing URLs:
const url = new URL('https://example.com?z=last&a=first&m=middle');
// Sort parameters alphabetically by key
url.searchParams.sort();
console.log(url.href);
// "https://example.com?a=first&m=middle&z=last"Building URLs
URL objects aren't just for reading: you can modify any part (except the read-only origin). Changes are validated and reflected immediately in href. This makes programmatic URL construction safe and straightforward.
const url = new URL('https://example.com');
// Modify the path
url.pathname = '/api/users';
// Add query parameters
url.searchParams.set('limit', '10');
url.searchParams.set('sort', 'name');
// Add a hash/fragment
url.hash = 'results';
console.log(url.href);
// "https://example.com/api/users?limit=10&sort=name#results"
// You can even change the protocol and hostname
url.protocol = 'http:';
url.hostname = 'localhost';
url.port = '3000';
console.log(url.href);
// "http://localhost:3000/api/users?limit=10&sort=name#results"Resolving Relative URLs
The URL constructor accepts a second argument: a base URL. This is invaluable for resolving relative paths, which is common when scraping websites, building link handlers, or working with APIs.
const base = 'https://example.com/docs/guide/';
// Relative to current directory
new URL('intro.html', base).href;
// "https://example.com/docs/guide/intro.html"
// Go up one directory
new URL('../images/logo.png', base).href;
// "https://example.com/docs/images/logo.png"
// Absolute path (starts from root)
new URL('/about', base).href;
// "https://example.com/about"
// Full URL ignores the base
new URL('https://other.com/page', base).href;
// "https://other.com/page"
// Real-world example: resolving links from a scraped page
const pageUrl = 'https://blog.example.com/posts/2024/my-article';
const links = ['../index.html', '/about', 'comments.html', 'https://twitter.com/share'];
links.map(link => new URL(link, pageUrl).href);
// [
// "https://blog.example.com/posts/2024/index.html",
// "https://blog.example.com/about",
// "https://blog.example.com/posts/2024/my-article/comments.html",
// "https://twitter.com/share"
// ]Real-World Examples
Let's look at how these APIs solve common tasks you'll encounter in web development. These patterns work in both browser JavaScript and Node.js.
Get Current Page's Parameters
One of the most common tasks: reading parameters from the current URL in a browser. Here are three approaches depending on your needs.
// Method 1: Using URL object (full URL parsing)
const url = new URL(window.location.href);
const searchTerm = url.searchParams.get('q');
const page = parseInt(url.searchParams.get('page') || '1', 10);
// Method 2: Using URLSearchParams directly (just the query string)
const params = new URLSearchParams(window.location.search);
const filter = params.get('filter');
// Method 3: Helper function for common use case
function getQueryParam(name, defaultValue = null) {
const params = new URLSearchParams(window.location.search);
return params.get(name) ?? defaultValue;
}
const sortBy = getQueryParam('sort', 'date'); // Returns 'date' if no ?sort paramMethod 1 parses the full URL, giving access to all components. Method 2 is more direct when you only need query parameters. Method 3 wraps the common pattern into a reusable helper with default values.
Update URL Without Page Reload
Single-page applications often need to update the URL to reflect state changes (filters, pagination, etc.) without triggering a full page reload. The History API makes this possible.
// Function to update a single URL parameter
function setUrlParam(key, value) {
const url = new URL(window.location.href);
if (value === null || value === undefined) {
url.searchParams.delete(key);
} else {
url.searchParams.set(key, value);
}
// Update URL without reload (adds to browser history)
window.history.pushState({}, '', url);
// Or: replace current history entry (no back button)
// window.history.replaceState({}, '', url);
}
// Usage
setUrlParam('page', '2'); // Add or update ?page=2
setUrlParam('filter', 'new'); // Add or update ?filter=new
setUrlParam('page', null); // Remove ?page
// Update multiple parameters at once
function setUrlParams(paramsObj) {
const url = new URL(window.location.href);
for (const [key, value] of Object.entries(paramsObj)) {
if (value === null || value === undefined) {
url.searchParams.delete(key);
} else {
url.searchParams.set(key, String(value));
}
}
window.history.pushState({}, '', url);
}
setUrlParams({ page: 3, sort: 'price', filter: null });The History API lets you update the URL without reloading the page. Use pushState to add history entries (users can go back), or replaceState to update silently. This is essential for SPAs with filtered lists or pagination.
Build API URLs Safely
When calling APIs, you often need to build URLs with multiple parameters. Here's a clean pattern:
function buildApiUrl(endpoint, params = {}) {
// Use your API base URL
const url = new URL(endpoint, 'https://api.example.com');
// Add each parameter (skipping null/undefined values)
for (const [key, value] of Object.entries(params)) {
if (value != null) { // Covers both null and undefined
url.searchParams.set(key, String(value));
}
}
return url.toString();
}
// Usage
const usersUrl = buildApiUrl('/users', {
limit: 20,
offset: 0,
active: true,
role: undefined // Will be skipped
});
// "https://api.example.com/users?limit=20&offset=0&active=true"
// Works great with fetch
const response = await fetch(buildApiUrl('/search', {
q: 'Tom & Jerry', // Special characters handled automatically
page: 1
}));This helper function encapsulates the common pattern of building API URLs with optional parameters. It skips null/undefined values and handles encoding automatically. The base URL as the second argument to URL resolves relative paths.
Check if a String is a Valid URL
The URL constructor throws an error for invalid URLs, making validation simple:
function isValidUrl(string) {
try {
new URL(string);
return true;
} catch {
return false;
}
}
isValidUrl('https://example.com'); // true
isValidUrl('https://example.com/path?q=1'); // true
isValidUrl('not-a-url'); // false
isValidUrl('example.com'); // false (no protocol)
isValidUrl(''); // false
// More specific: check if it's an HTTP(S) URL
function isHttpUrl(string) {
try {
const url = new URL(string);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch {
return false;
}
}
isHttpUrl('https://example.com'); // true
isHttpUrl('ftp://files.com'); // false
isHttpUrl('javascript:alert(1)'); // falseThe try-catch pattern leverages the fact that URL throws on invalid input. The isHttpUrl variant is stricter, rejecting non-HTTP schemes like javascript:, which is essential for security when handling user-provided URLs.
Compare Two URLs
URLs that look different might actually point to the same resource. Here's how to compare them meaningfully:
function urlsMatch(url1, url2, options = {}) {
const { ignoreHash = true, ignoreTrailingSlash = true } = options;
const a = new URL(url1);
const b = new URL(url2);
if (ignoreHash) {
a.hash = '';
b.hash = '';
}
if (ignoreTrailingSlash) {
a.pathname = a.pathname.replace(/\/$/, '');
b.pathname = b.pathname.replace(/\/$/, '');
}
// Sort params for consistent comparison
a.searchParams.sort();
b.searchParams.sort();
return a.href === b.href;
}
urlsMatch(
'https://example.com/page?b=2&a=1#section',
'https://example.com/page/?a=1&b=2'
); // true (hash ignored, trailing slash ignored, params sorted)The comparison function normalizes URLs before comparing: removing fragments, trailing slashes, and sorting parameters ensures semantically equivalent URLs match. This is useful for caching, deduplication, and analytics.
Automatic Encoding
One of the best features of the URL API: encoding is handled automatically. You never need to call encodeURIComponent() manually.
const url = new URL('https://example.com/search');
// Special characters are encoded automatically when setting values
url.searchParams.set('q', 'Tom & Jerry');
url.searchParams.set('filter', 'price > 100');
url.searchParams.set('emoji', '🎉');
console.log(url.href);
// "https://example.com/search?q=Tom+%26+Jerry&filter=price+%3E+100&emoji=%F0%9F%8E%89"
// And decoded automatically when reading values
url.searchParams.get('q'); // "Tom & Jerry" (decoded)
url.searchParams.get('emoji'); // "🎉" (decoded)
// This also works for path segments
url.pathname = '/files/my report.pdf';
console.log(url.href);
// "https://example.com/files/my%20report.pdf?q=Tom+%26+Jerry&..."This automatic encoding prevents a whole class of bugs. Ampersands, equals signs, Unicode characters: they're all handled correctly. Values are decoded when you read them back, so you work with normal strings throughout your code.
Browser Support
The URL API is well-supported in all modern environments:
- Chrome 32+ (January 2014)
- Firefox 26+ (December 2013)
- Safari 10+ (September 2016)
- Edge 12+ (July 2015)
- Node.js 10+ (via built-in module or global)
In Node.js versions before 18, you may need to import it:
// Node.js < 18
const { URL, URLSearchParams } = require('url');
// Node.js 18+ and browsers
// URL and URLSearchParams are globals, no import neededWhile both APIs work the same way across environments, you may need to import them in older Node.js versions. Modern Node.js (18+) and all browsers make them available globally.
Using URLSearchParams Standalone
You don't always need a full URL object. URLSearchParams can work independently, which is useful for parsing and building query strings:
// Parse a query string (with or without leading ?)
const params = new URLSearchParams('?name=John&age=30');
params.get('name'); // "John"
// Create from an object
const params2 = new URLSearchParams({
search: 'hello world',
page: '1'
});
params2.toString(); // "search=hello+world&page=1"
// Create from an array of pairs (preserves duplicate keys)
const params3 = new URLSearchParams([
['color', 'red'],
['color', 'blue'],
['size', 'large']
]);
params3.toString(); // "color=red&color=blue&size=large"
// Convert to a plain object (loses duplicate keys!)
const obj = Object.fromEntries(params3);
// { color: 'blue', size: 'large' } - only last 'color' value kept
// To preserve all values, use reduce
const fullObj = [...params3].reduce((acc, [key, value]) => {
if (acc[key]) {
acc[key] = [].concat(acc[key], value);
} else {
acc[key] = value;
}
return acc;
}, {});
// { color: ['red', 'blue'], size: 'large' }The standalone URLSearchParams is useful when you already have a query string or want to build one without a full URL. The conversion to objects shows an important gotcha: Object.fromEntries() loses duplicate keys, so use the reduce pattern when duplicates matter.
Frequently Asked Questions
Official Documentation
- MDN: URL API — Complete reference with all methods
- MDN: URLSearchParams — Query string manipulation reference
- WHATWG URL Standard — The specification that browsers implement