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.searchParamsto read and modify query parameters; usegetAll()when a name can occur more than once. append()adds another value, whileset()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; usegetAll()to retrieve every value. - Using
set()when you intend to add a duplicate.set()replaces existing values for that key; useappend()to add another. - Concatenating raw values into the query string. Use
searchParamsso 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
URLobjects while changing their components; serialize with.hrefor.toString()at the boundary where a string is needed. - Decide explicitly whether parameters may repeat, and choose
get,getAll,set, orappendaccordingly. - 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.
