TidyURLs
Go to Tools
BLOG
The TidyURLsJournal

Practical guides, tutorials, and insights about URLs, SEO, web development, and online marketing. Learn, troubleshoot, and get the most of the web.

Back to the Journal
TidyURLs Journal

How to Parse and Modify URLs with the JavaScript URL API

Practical insights, clear explanations, and useful ideas for understanding the web.

October 26, 20265 min read5 topics
How to Parse and Modify URLs with the JavaScript URL API
Explore the details
#JavaScript#URL API#URLSearchParams#Query Strings#Web Development
October 26, 20265 min read

Parsing URLs with string operations can break when a link contains encoded characters, duplicate query parameters, or a fragment. JavaScript’s URL and URLSearchParams APIs handle these parts structurally, making it easier to resolve relative links and update query values without rebuilding the whole URL by hand.

Key Takeaways

  • Use new URL(input, base) to parse an absolute URL or resolve a relative one.
  • Use url.searchParams to read and modify query parameters; use getAll() when a name can occur more than once.
  • append() adds another value, while set() replaces the named parameter’s values with one value.
  • Let the URL APIs serialize query values instead of concatenating unencoded strings.
  • Parsing a URL is not the same as trusting it. Check the scheme and, where appropriate, the allowed origin before navigating to a user-supplied URL.

Parse a URL into its components

The URL constructor parses a URL and exposes its components as properties. In a browser or a current Node.js runtime, URL is available globally.

const url = new URL("https://shop.example/products?category=books#details");

console.log(url.origin);   // "https://shop.example"
console.log(url.pathname); // "/products"
console.log(url.search);   // "?category=books"
console.log(url.hash);     // "#details"

The constructor throws a TypeError if it cannot parse the input as a URL. For an absolute URL, pass the complete URL. For a relative URL, also provide a base, as shown below.

Resolve relative URLs against a base

A relative reference is interpreted in relation to the base URL. The base’s path matters: a base ending in a filename and a base ending in a slash resolve relative paths differently.

const base = "https://example.com/docs/page.html";

console.log(new URL("../images/logo.svg", base).href);
// "https://example.com/images/logo.svg"

console.log(new URL("images/logo.svg", "https://example.com/docs/").href);
// "https://example.com/docs/images/logo.svg"

A leading slash starts at the origin’s root. For example, new URL("/images/logo.svg", base) resolves to https://example.com/images/logo.svg.

Read and change query parameters

URLSearchParams is available through url.searchParams. It decodes values when reading them and serializes them when the query is changed.

const url = new URL(
  "https://shop.example/products?tag=js&tag=urls&q=red%20blue#details"
);

console.log(url.searchParams.get("q"));
// "red blue"

console.log(url.searchParams.get("tag"));
// "js" — get() returns the first matching value

console.log(url.searchParams.getAll("tag"));
// ["js", "urls"]

url.searchParams.append("tag", "web");
url.searchParams.set("q", "red & blue");

console.log(url.href);
// "https://shop.example/products?tag=js&tag=urls&q=red+%26+blue&tag=web#details"

Use getAll() when a parameter can repeat. append() adds a value without removing existing values. set() leaves one value for the named parameter, replacing its existing values. Use delete() to remove a parameter:

url.searchParams.delete("tag");

Changes to searchParams update the URL’s query string; the path and fragment remain part of the same URL. Note that query serialization uses form-style encoding: for example, a space is serialized as +, and & in a value is percent-encoded. Read values through searchParams rather than depending on one particular encoded spelling.

Modify a URL without losing its other parts

Parse the input, update the specific query parameters, and serialize the URL when finished. This example accepts only HTTPS URLs on one known origin before changing the link. The origin check is an application policy; replace it with the origins your application actually permits.

const allowedOrigin = "https://www.example.com";

function updateLink(input) {
  const url = new URL(input, allowedOrigin);

  if (
    url.protocol !== "https:" ||
    url.origin !== allowedOrigin ||
    url.username !== "" ||
    url.password !== ""
  ) {
    throw new Error("URL is not an allowed HTTPS link");
  }

  url.searchParams.set("page", "2");
  url.searchParams.delete("utm_source");

  return url.href;
}

console.log(updateLink("/products?utm_source=newsletter#reviews"));
// "https://www.example.com/products?page=2#reviews"

The relative input resolves against allowedOrigin; the checks then ensure that the parsed result is an HTTPS URL on that exact origin and contains no user information. If your application accepts external URLs, use an explicit allowlist and validate the parsed URL before using it. Do not assume that successfully constructing a URL makes it safe to navigate to, fetch, or trust.

Why string splitting is fragile

Code such as input.split("?") treats a URL as an arbitrary string rather than structured data. It can mishandle fragments, repeated query keys, and values that contain encoded delimiters. A hand-built query like "?q=" + value can also produce incorrect results when value contains characters such as & or #.

Use URL to parse and resolve the address, and URLSearchParams to read and mutate the query. If you need to put an individual value into a path segment, encode that segment rather than encoding the entire URL; URL components have different rules.

For a quick visual check of URL components, you can also inspect a URL with TidyURLs. Treat that as a convenience for inspection, not as a replacement for validation in your application.

Common Mistakes

  • Calling new URL() on a relative path without a base. Provide a base URL when the input may be relative.
  • Using get() when duplicate keys matter. It returns only the first value; use getAll() to retrieve every value.
  • Using set() when you intend to add a duplicate. set() replaces existing values for that key; use append() to add another.
  • Concatenating raw values into the query string. Use searchParams so reserved characters are serialized as data rather than mistaken for query syntax.
  • Treating successful parsing as a security check. Validate allowed protocols and origins before navigation. If a server will fetch a supplied URL, additional SSRF protections are needed; URL parsing and hostname checks alone are not sufficient.

Best Practices

  • Keep URLs as URL objects while changing their components; serialize with .href or .toString() at the boundary where a string is needed.
  • Decide explicitly whether parameters may repeat, and choose get, getAll, set, or append accordingly.
  • Resolve relative links against the correct base, especially when the base path may or may not end with a slash.
  • For untrusted destinations, apply an application-specific scheme and origin policy after parsing. Do not allow arbitrary protocols just because the constructor accepts the input.

Conclusion

The JavaScript URL API handles parsing and relative resolution, while URLSearchParams provides focused operations for query strings, including repeated keys and safe serialization. Using them together avoids many string-splitting errors; validating the resulting destination remains your responsibility.

References