DEV Community

Cover image for Mastering JavaScript's URL() and URLSearchParams: A Complete Guide
Christopher Sesugh
Christopher Sesugh

Posted on

Mastering JavaScript's URL() and URLSearchParams: A Complete Guide

Introduction

Working with URLs in JavaScript used to be a messy affair involving string manipulation, regular expressions, and brittle parsing logic. The modern URL() and URLSearchParams APIs changed everything, providing robust, standardized interfaces for URL manipulation that solve countless headaches developers face daily.

In this comprehensive guide, we'll explore these APIs in depth, understanding not just how they work, but why they exist and the specific problems they solve.

The URL() Constructor: Understanding Web Addresses

What is URL()?

The URL() constructor creates URL objects that represent and parse Uniform Resource Locators. It takes a URL string (and optionally a base URL) and returns an object with properties representing each component of the URL.

Basic Syntax

new URL(url)
new URL(url, base)
Enter fullscreen mode Exit fullscreen mode

Parameters:

  • url (required): An absolute or relative URL string
  • base (optional): A base URL string to resolve relative URLs against

Anatomy of a URL Object

When you create a URL object, it automatically parses the URL into its constituent parts:

const url = new URL('https://john:secret@example.com:8080/path/to/page?name=John&age=30#section');

console.log(url.href);       // "https://john:secret@example.com:8080/path/to/page?name=John&age=30#section"
console.log(url.protocol);   // "https:"
console.log(url.username);   // "john"
console.log(url.password);   // "secret"
console.log(url.host);       // "example.com:8080"
console.log(url.hostname);   // "example.com"
console.log(url.port);       // "8080"
console.log(url.pathname);   // "/path/to/page"
console.log(url.search);     // "?name=John&age=30"
console.log(url.hash);       // "#section"
console.log(url.origin);     // "https://example.com:8080"
Enter fullscreen mode Exit fullscreen mode

Key Properties Explained

1. href - The complete URL string. This is both readable and writable:

const url = new URL('https://example.com/page');
url.href = 'https://newsite.com/newpage';
console.log(url.hostname); // "newsite.com"
Enter fullscreen mode Exit fullscreen mode

2. protocol - The scheme of the URL (always includes the colon):

const url = new URL('https://example.com');
url.protocol = 'http:';
console.log(url.href); // "http://example.com"
Enter fullscreen mode Exit fullscreen mode

3. username and password - Credentials in the URL (rarely used in modern web development due to security concerns):

const url = new URL('https://example.com');
url.username = 'admin';
url.password = 'pass123';
console.log(url.href); // "https://admin:pass123@example.com"
Enter fullscreen mode Exit fullscreen mode

4. host vs hostname - A critical distinction:

  • host includes the port number
  • hostname is just the domain name
const url = new URL('https://example.com:3000');
console.log(url.host);     // "example.com:3000"
console.log(url.hostname); // "example.com"
Enter fullscreen mode Exit fullscreen mode

5. port - The port number as a string (empty string if using default port):

const url = new URL('https://example.com:8080');
console.log(url.port); // "8080"

const url2 = new URL('https://example.com');
console.log(url2.port); // "" (empty string, not "443")
Enter fullscreen mode Exit fullscreen mode

6. pathname - The path section, always starting with /:

const url = new URL('https://example.com/users/123/profile');
console.log(url.pathname); // "/users/123/profile"

url.pathname = '/posts/456';
console.log(url.href); // "https://example.com/posts/456"
Enter fullscreen mode Exit fullscreen mode

7. search - The query string including the ?:

const url = new URL('https://example.com?foo=bar&baz=qux');
console.log(url.search); // "?foo=bar&baz=qux"
Enter fullscreen mode Exit fullscreen mode

8. hash - The fragment identifier including the #:

const url = new URL('https://example.com/page#section-2');
console.log(url.hash); // "#section-2"
Enter fullscreen mode Exit fullscreen mode

9. origin - Read-only property combining protocol, hostname, and port:

const url = new URL('https://example.com:8080/path?query');
console.log(url.origin); // "https://example.com:8080"
Enter fullscreen mode Exit fullscreen mode

Working with Relative URLs

One of the most powerful features of URL() is resolving relative URLs against a base:

// Resolving relative paths
const base = 'https://example.com/users/123/profile';

const url1 = new URL('edit', base);
console.log(url1.href); // "https://example.com/users/123/edit"

const url2 = new URL('../456/posts', base);
console.log(url2.href); // "https://example.com/users/456/posts"

const url3 = new URL('/api/data', base);
console.log(url3.href); // "https://example.com/api/data"

const url4 = new URL('?page=2', base);
console.log(url4.href); // "https://example.com/users/123/profile?page=2"
Enter fullscreen mode Exit fullscreen mode

Key Methods of URL

1. toString() - Returns the complete URL as a string (equivalent to href):

const url = new URL('https://example.com/page');
console.log(url.toString()); // "https://example.com/page"
console.log(String(url));    // "https://example.com/page"
Enter fullscreen mode Exit fullscreen mode

2. toJSON() - Returns the URL string for JSON serialization:

const url = new URL('https://example.com/api');
console.log(JSON.stringify({ endpoint: url })); 
// {"endpoint":"https://example.com/api"}
Enter fullscreen mode Exit fullscreen mode

Static Methods

URL.canParse(url, base) - Check if a URL is valid without throwing an error (modern browsers):

console.log(URL.canParse('https://example.com')); // true
console.log(URL.canParse('not a url'));           // false
console.log(URL.canParse('/relative', 'https://example.com')); // true
Enter fullscreen mode Exit fullscreen mode

URL.createObjectURL(blob) - Creates a temporary URL for Blob or File objects:

const blob = new Blob(['Hello'], { type: 'text/plain' });
const blobUrl = URL.createObjectURL(blob);
console.log(blobUrl); // "blob:https://example.com/550e8400-e29b-41d4-a716-446655440000"

// Don't forget to revoke when done
URL.revokeObjectURL(blobUrl);
Enter fullscreen mode Exit fullscreen mode

URLSearchParams: The Query String Swiss Army Knife

What is URLSearchParams?

URLSearchParams is an interface for working with query strings. It provides methods to read, add, modify, and delete query parameters without manual string manipulation.

Creating URLSearchParams

There are multiple ways to create a URLSearchParams object:

1. From a query string:

const params = new URLSearchParams('name=John&age=30&age=31');
Enter fullscreen mode Exit fullscreen mode

2. From an object (modern approach):

const params = new URLSearchParams({
  name: 'John',
  age: 30,
  city: 'New York'
});
Enter fullscreen mode Exit fullscreen mode

3. From an array of key-value pairs:

const params = new URLSearchParams([
  ['name', 'John'],
  ['age', '30'],
  ['hobby', 'coding'],
  ['hobby', 'reading']
]);
Enter fullscreen mode Exit fullscreen mode

4. From a URL object's searchParams property:

const url = new URL('https://example.com?name=John&age=30');
const params = url.searchParams; // Direct reference, not a copy!
Enter fullscreen mode Exit fullscreen mode

Core Methods: Reading Parameters

1. get(name) - Retrieves the first value for a parameter:

const params = new URLSearchParams('name=John&age=30&age=31');
console.log(params.get('name')); // "John"
console.log(params.get('age'));  // "30" (first value only)
console.log(params.get('city')); // null (doesn't exist)
Enter fullscreen mode Exit fullscreen mode

2. getAll(name) - Retrieves all values for a parameter as an array:

const params = new URLSearchParams('tag=js&tag=web&tag=api');
console.log(params.getAll('tag')); // ["js", "web", "api"]
console.log(params.getAll('missing')); // [] (empty array)
Enter fullscreen mode Exit fullscreen mode

3. has(name) - Checks if a parameter exists:

const params = new URLSearchParams('name=John&age=30');
console.log(params.has('name')); // true
console.log(params.has('city')); // false
Enter fullscreen mode Exit fullscreen mode

4. keys() - Returns an iterator of all parameter names:

const params = new URLSearchParams('name=John&age=30&city=NYC');
for (const key of params.keys()) {
  console.log(key); // "name", "age", "city"
}

// Convert to array
console.log([...params.keys()]); // ["name", "age", "city"]
Enter fullscreen mode Exit fullscreen mode

5. values() - Returns an iterator of all parameter values:

const params = new URLSearchParams('name=John&age=30');
for (const value of params.values()) {
  console.log(value); // "John", "30"
}
Enter fullscreen mode Exit fullscreen mode

6. entries() - Returns an iterator of [key, value] pairs:

const params = new URLSearchParams('name=John&age=30');
for (const [key, value] of params.entries()) {
  console.log(`${key}: ${value}`);
  // "name: John"
  // "age: 30"
}

// URLSearchParams is directly iterable
for (const [key, value] of params) {
  console.log(`${key}: ${value}`);
}
Enter fullscreen mode Exit fullscreen mode

7. forEach(callback) - Executes a callback for each parameter:

const params = new URLSearchParams('name=John&age=30');
params.forEach((value, key) => {
  console.log(`${key} = ${value}`);
});
Enter fullscreen mode Exit fullscreen mode

Core Methods: Modifying Parameters

1. set(name, value) - Sets a parameter, replacing all existing values:

const params = new URLSearchParams('name=John&age=30&age=31');
params.set('age', '25');
console.log(params.toString()); // "name=John&age=25"

params.set('city', 'NYC');
console.log(params.toString()); // "name=John&age=25&city=NYC"
Enter fullscreen mode Exit fullscreen mode

2. append(name, value) - Adds a parameter without removing existing ones:

const params = new URLSearchParams('tag=js');
params.append('tag', 'web');
params.append('tag', 'api');
console.log(params.toString()); // "tag=js&tag=web&tag=api"
Enter fullscreen mode Exit fullscreen mode

3. delete(name) - Removes a parameter:

const params = new URLSearchParams('name=John&age=30&city=NYC');
params.delete('age');
console.log(params.toString()); // "name=John&city=NYC"
Enter fullscreen mode Exit fullscreen mode

Modern delete() with value parameter:

const params = new URLSearchParams('tag=js&tag=web&tag=js');
params.delete('tag', 'js'); // Remove only 'tag=js' entries
console.log(params.toString()); // "tag=web"
Enter fullscreen mode Exit fullscreen mode

4. sort() - Sorts parameters alphabetically by key:

const params = new URLSearchParams('zebra=1&apple=2&mango=3');
params.sort();
console.log(params.toString()); // "apple=2&mango=3&zebra=1"
Enter fullscreen mode Exit fullscreen mode

5. toString() - Converts to query string (without leading ?):

const params = new URLSearchParams({ name: 'John', age: 30 });
console.log(params.toString()); // "name=John&age=30"
console.log('?' + params.toString()); // "?name=John&age=30"
Enter fullscreen mode Exit fullscreen mode

Integration with URL

The real power emerges when combining URL and URLSearchParams:

const url = new URL('https://api.example.com/users');

// url.searchParams is a live URLSearchParams object
url.searchParams.set('page', '2');
url.searchParams.set('limit', '10');
url.searchParams.append('sort', 'name');

console.log(url.href); 
// "https://api.example.com/users?page=2&limit=10&sort=name"

// Modifications to searchParams immediately affect the URL
url.searchParams.delete('limit');
console.log(url.href); 
// "https://api.example.com/users?page=2&sort=name"
Enter fullscreen mode Exit fullscreen mode

Real-World Problems They Solve

Problem 1: Safe Query String Parsing

Before URL APIs:

// Fragile and error-prone
function getQueryParam(url, param) {
  const regex = new RegExp('[?&]' + param + '=([^&#]*)');
  const results = regex.exec(url);
  return results ? decodeURIComponent(results[1]) : null;
}

// Doesn't handle multiple values, encoding issues, etc.
Enter fullscreen mode Exit fullscreen mode

With URLSearchParams:

const url = new URL(window.location.href);
const paramValue = url.searchParams.get('param');
// Handles encoding, multiple values, and edge cases automatically
Enter fullscreen mode Exit fullscreen mode

Problem 2: Building Dynamic API URLs

Before URL APIs:

// Manual string concatenation - prone to bugs
let apiUrl = 'https://api.example.com/search?';
if (query) apiUrl += 'q=' + encodeURIComponent(query) + '&';
if (page) apiUrl += 'page=' + page + '&';
if (filters) {
  filters.forEach(f => {
    apiUrl += 'filter=' + encodeURIComponent(f) + '&';
  });
}
apiUrl = apiUrl.slice(0, -1); // Remove trailing &
Enter fullscreen mode Exit fullscreen mode

With URL APIs:

const apiUrl = new URL('https://api.example.com/search');
if (query) apiUrl.searchParams.set('q', query);
if (page) apiUrl.searchParams.set('page', page);
if (filters) {
  filters.forEach(f => apiUrl.searchParams.append('filter', f));
}
// Clean, readable, no encoding worries
Enter fullscreen mode Exit fullscreen mode

Problem 3: URL Modification Without Reloading

Updating browser URL state:

// Update URL without page reload (for SPAs)
function updateFilters(filters) {
  const url = new URL(window.location);
  url.searchParams.delete('filter'); // Clear existing
  filters.forEach(f => url.searchParams.append('filter', f));

  window.history.pushState({}, '', url);
}
Enter fullscreen mode Exit fullscreen mode

Problem 4: Handling Complex Query Strings

Working with arrays and multiple values:

// Building a search URL with multiple filters
const searchUrl = new URL('https://shop.example.com/products');
searchUrl.searchParams.set('category', 'electronics');
searchUrl.searchParams.append('color', 'black');
searchUrl.searchParams.append('color', 'silver');
searchUrl.searchParams.set('minPrice', '100');
searchUrl.searchParams.set('maxPrice', '500');

console.log(searchUrl.href);
// "https://shop.example.com/products?category=electronics&color=black&color=silver&minPrice=100&maxPrice=500"

// Reading multiple values on the backend/client
const colors = searchUrl.searchParams.getAll('color');
console.log(colors); // ["black", "silver"]
Enter fullscreen mode Exit fullscreen mode

Problem 5: Form Data to URL Query

Converting form data to URL query string:

const formData = new FormData(document.querySelector('form'));
const searchParams = new URLSearchParams(formData);

const apiUrl = new URL('https://api.example.com/submit');
apiUrl.search = searchParams.toString();

fetch(apiUrl)
  .then(response => response.json())
  .then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

Problem 6: URL Validation and Sanitization

Safely validating URLs:

function isValidUrl(string) {
  try {
    new URL(string);
    return true;
  } catch (err) {
    return false;
  }
}

console.log(isValidUrl('https://example.com')); // true
console.log(isValidUrl('not a url')); // false

// Or with modern browsers:
console.log(URL.canParse('https://example.com')); // true
Enter fullscreen mode Exit fullscreen mode

Practical Examples

Example 1: Pagination Helper

class PaginationHelper {
  constructor(baseUrl) {
    this.url = new URL(baseUrl);
  }

  setPage(page) {
    this.url.searchParams.set('page', page);
    return this;
  }

  setLimit(limit) {
    this.url.searchParams.set('limit', limit);
    return this;
  }

  setSort(field, order = 'asc') {
    this.url.searchParams.set('sort', field);
    this.url.searchParams.set('order', order);
    return this;
  }

  getUrl() {
    return this.url.href;
  }
}

const pagination = new PaginationHelper('https://api.example.com/users');
const url = pagination
  .setPage(2)
  .setLimit(20)
  .setSort('name', 'desc')
  .getUrl();

console.log(url);
// "https://api.example.com/users?page=2&limit=20&sort=name&order=desc"
Enter fullscreen mode Exit fullscreen mode

Example 2: Search Filter Manager

class SearchFilters {
  constructor(currentUrl = window.location.href) {
    this.url = new URL(currentUrl);
  }

  addFilter(key, value) {
    this.url.searchParams.append(key, value);
    return this;
  }

  removeFilter(key, value = null) {
    if (value === null) {
      this.url.searchParams.delete(key);
    } else {
      // Remove specific value
      const values = this.url.searchParams.getAll(key);
      this.url.searchParams.delete(key);
      values.filter(v => v !== value).forEach(v => {
        this.url.searchParams.append(key, v);
      });
    }
    return this;
  }

  getFilters(key) {
    return this.url.searchParams.getAll(key);
  }

  clearAll() {
    // Keep only specific params like page
    const page = this.url.searchParams.get('page');
    this.url.search = '';
    if (page) this.url.searchParams.set('page', page);
    return this;
  }

  apply() {
    window.history.pushState({}, '', this.url);
    // Trigger filter update event
    window.dispatchEvent(new CustomEvent('filtersChanged'));
  }

  toString() {
    return this.url.href;
  }
}

// Usage
const filters = new SearchFilters();
filters
  .addFilter('category', 'electronics')
  .addFilter('brand', 'apple')
  .addFilter('brand', 'samsung')
  .apply();
Enter fullscreen mode Exit fullscreen mode

Example 3: API Client with Query Builder

class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }

  buildUrl(endpoint, params = {}) {
    const url = new URL(endpoint, this.baseUrl);

    Object.entries(params).forEach(([key, value]) => {
      if (Array.isArray(value)) {
        value.forEach(v => url.searchParams.append(key, v));
      } else if (value !== null && value !== undefined) {
        url.searchParams.set(key, value);
      }
    });

    return url.href;
  }

  async get(endpoint, params = {}) {
    const url = this.buildUrl(endpoint, params);
    const response = await fetch(url);
    return response.json();
  }
}

// Usage
const api = new ApiClient('https://api.example.com');

api.get('/products', {
  category: 'electronics',
  tags: ['new', 'featured'],
  minPrice: 100,
  maxPrice: 1000,
  page: 1
});
// Fetches: https://api.example.com/products?category=electronics&tags=new&tags=featured&minPrice=100&maxPrice=1000&page=1
Enter fullscreen mode Exit fullscreen mode

Example 4: URL State Synchronizer

class UrlStateSync {
  constructor() {
    this.url = new URL(window.location);
    this.listeners = new Map();
  }

  setState(key, value) {
    if (value === null || value === undefined || value === '') {
      this.url.searchParams.delete(key);
    } else {
      this.url.searchParams.set(key, value);
    }

    this.updateUrl();
    this.notifyListeners(key, value);
  }

  getState(key) {
    return this.url.searchParams.get(key);
  }

  updateUrl() {
    window.history.replaceState({}, '', this.url);
  }

  onChange(key, callback) {
    if (!this.listeners.has(key)) {
      this.listeners.set(key, []);
    }
    this.listeners.get(key).push(callback);
  }

  notifyListeners(key, value) {
    if (this.listeners.has(key)) {
      this.listeners.get(key).forEach(callback => callback(value));
    }
  }
}

// Usage
const urlState = new UrlStateSync();

urlState.onChange('theme', (theme) => {
  document.body.className = theme;
});

urlState.setState('theme', 'dark'); // Updates URL and applies theme
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls and Best Practices

Pitfall 1: Forgetting URL Encoding

// ❌ Wrong - manual encoding issues
const url = 'https://api.example.com/search?q=' + query;

// ✅ Correct - automatic encoding
const url = new URL('https://api.example.com/search');
url.searchParams.set('q', query);
Enter fullscreen mode Exit fullscreen mode

Pitfall 2: Modifying searchParams Reference

const url = new URL('https://example.com?name=John');

// url.searchParams is a live reference
const params = url.searchParams;
params.set('age', '30');

console.log(url.href); // Changed! "https://example.com?name=John&age=30"

// To get an independent copy:
const paramsCopy = new URLSearchParams(url.searchParams);
Enter fullscreen mode Exit fullscreen mode

Pitfall 3: Using set() vs append()

const params = new URLSearchParams();

// set() replaces all values
params.set('tag', 'js');
params.set('tag', 'web');
console.log(params.toString()); // "tag=web" (only one)

// append() adds multiple values
params.append('tag', 'js');
params.append('tag', 'web');
console.log(params.toString()); // "tag=js&tag=web"
Enter fullscreen mode Exit fullscreen mode

Best Practice 1: Always Validate URLs

function safeUrlParse(urlString, base) {
  try {
    return new URL(urlString, base);
  } catch (error) {
    console.error('Invalid URL:', urlString);
    return null;
  }
}
Enter fullscreen mode Exit fullscreen mode

Best Practice 2: Use searchParams for Query Manipulation

// ❌ Avoid direct search manipulation
url.search = '?name=John&age=30';

// ✅ Use searchParams
url.searchParams.set('name', 'John');
url.searchParams.set('age', '30');
Enter fullscreen mode Exit fullscreen mode

Best Practice 3: Clone URLs When Needed

function modifyUrlWithoutMutating(originalUrl, modifications) {
  const url = new URL(originalUrl.href); // Create new instance

  Object.entries(modifications).forEach(([key, value]) => {
    url.searchParams.set(key, value);
  });

  return url;
}
Enter fullscreen mode Exit fullscreen mode

Browser Support and Polyfills

The URL() and URLSearchParams APIs have excellent browser support:

  • URL: Supported in all modern browsers (Chrome 32+, Firefox 26+, Safari 7+, Edge 12+)
  • URLSearchParams: Supported in all modern browsers (Chrome 49+, Firefox 44+, Safari 10.3+, Edge 17+)

For older browsers, you can use the url-polyfill package:

import 'url-polyfill';

// Now URL and URLSearchParams work in older browsers
Enter fullscreen mode Exit fullscreen mode

Conclusion

The URL() and URLSearchParams APIs represent a massive improvement over manual URL string manipulation. They provide:

  • Type safety: Automatic encoding and validation
  • Simplicity: Intuitive methods for common operations
  • Reliability: Handle edge cases correctly
  • Maintainability: Self-documenting code
  • Standards compliance: Following web standards

Whether you're building SPAs, working with APIs, managing client-side routing, or handling form submissions, these APIs should be your go-to tools for URL manipulation.


Take Your JavaScript Skills Further

If you found this deep dive valuable, you'll love what we're building at TekBreed – a comprehensive learning platform designed specifically for software engineers who want to master modern web development.

At TekBreed, we create in-depth, practical courses that go beyond surface-level tutorials. We focus on the "why" behind the code, real-world applications, and the tiny details that separate good developers from great ones.

🚀 The waitlist is now live at tekbreed.com

We're currently in active development and planning to launch in Q1 2026. Join the waitlist to:

  • Get early access to our platform
  • Receive exclusive content and tutorials
  • Help shape the courses we create
  • Be part of a community of passionate engineers

We're building something special for developers who refuse to settle for shallow understanding. See you at TekBreed! 🎯

Top comments (0)