The URL API in JavaScript, Explained
The URL API parses, builds, and validates URLs natively in JavaScript — no regex needed. How URL and URLSearchParams work, with practical examples.
The URL API is a built-in JavaScript interface for parsing, constructing, and manipulating URLs without regular expressions or manual string splitting. It ships in every modern browser and in Node.js, and it turns error-prone string surgery — finding the query string, decoding a parameter, swapping a path segment — into a handful of property reads and method calls on a URL object.
Before this API existed, developers reached for regex or String.split() to pull apart a URL, which broke on edge cases like encoded characters, missing protocols, or unusual port numbers. The URL constructor parses per the WHATWG URL Standard, so it handles those edge cases the same way browsers do.
Constructing a URL object
const url = new URL("https://example.com:8080/products?id=42&sort=price#reviews");
Passing a malformed string throws a TypeError, which makes the constructor a decent validation check on its own — wrap it in try/catch if you’re accepting user input.
A second, optional argument lets you resolve a relative URL against a base:
const link = new URL("/blog/javascript-closures-explained/", "https://lycoristechnologies.com");
This is the same resolution logic used for <a href> and fetch(), so it’s a reliable way to turn a relative path into an absolute one before sending it somewhere that needs a full address.
The pieces of a parsed URL
Once constructed, the object exposes every component as a separate, readable and writable property:
| Property | Value for the example above |
|---|---|
href | Full URL string |
protocol | "https:" |
hostname | "example.com" |
port | "8080" |
pathname | "/products" |
search | "?id=42&sort=price" |
hash | "#reviews" |
origin | "https://example.com:8080" |
Each of these is writable, and reassigning one updates href automatically:
url.pathname = "/products/updated";
url.hash = "";
console.log(url.href); // reflects both changes
This is a common pattern in client-side routers built without a framework — see the piece on the History API and client-side routing for how these primitives combine to build navigation without a full page reload.
Working with query strings via URLSearchParams
Query strings deserve their own interface because key=value&key2=value2 encoding has its own quirks — repeated keys, encoded spaces (+ versus %20), and ordering. url.searchParams returns a URLSearchParams instance that handles all of it:
url.searchParams.get("id"); // "42"
url.searchParams.set("sort", "date"); // replaces the existing value
url.searchParams.append("tag", "sale"); // adds a second entry, doesn't overwrite
url.searchParams.delete("id");
url.searchParams.has("sort"); // true
for (const [key, value] of url.searchParams) {
console.log(key, value);
}
Because searchParams is a live view tied to the parent URL object, calling set or delete immediately updates url.search and url.href. You can also construct a standalone URLSearchParams from a plain object, an array of pairs, or a raw query string, which is handy for building a fetch() body or query string from scratch without string concatenation:
const params = new URLSearchParams({ page: "2", limit: "20" });
fetch(`/api/results?${params}`);
That pairs naturally with the Fetch API — building the query string with URLSearchParams avoids the common bug of forgetting to encode a value that contains an & or #.
Encoding without the footguns
Before the URL API, developers used encodeURIComponent() and decodeURIComponent() directly, which is still correct for encoding a single value but doesn’t know anything about URL structure — it’s easy to double-encode a value or encode an entire URL when you only meant to encode one segment. URLSearchParams and URL apply the right encoding for each component automatically: path segments, query values, and fragments each have slightly different rules for which characters need escaping, and the API tracks that so you don’t have to.
Where this fits alongside fetch and routing
The URL API doesn’t replace fetch() or a router — it’s the parsing layer underneath both. A typical use is validating and normalizing a redirect target before calling location.assign(), or reading route parameters out of location.search in a single-page app that doesn’t use a framework router. If you’re working with CORS, comparing origin values with new URL(a).origin === new URL(b).origin is more reliable than string comparison, since it normalizes case and default ports.
It’s also useful server-side. Node.js’s http module hands you a raw request path, and parsing it with new URL(req.url, \http://${req.headers.host}`)` gives you the same structured object you’d get in the browser, which is one reason the API was added to Node rather than left as a browser-only feature.
A quick example: building a shareable link
function buildShareLink(baseUrl, articleSlug, utmSource) {
const url = new URL(`/blog/${articleSlug}/`, baseUrl);
url.searchParams.set("utm_source", utmSource);
return url.href;
}
buildShareLink("https://lycoristechnologies.com", "what-is-a-webhook", "newsletter");
// "https://lycoristechnologies.com/blog/what-is-a-webhook/?utm_source=newsletter"
Three lines replace what used to be manual string concatenation with conditional ? versus & logic depending on whether a query string already existed — a classic source of bugs.
The takeaway
The URL API gives JavaScript a native, spec-compliant way to parse, build, and edit URLs: a URL object for the structure, and URLSearchParams for the query string. Reach for it instead of regex or manual splitting any time you’re validating a link, reading route parameters, or assembling a request URL for an API call — it handles encoding and edge cases the same way the browser does, which regex rarely does correctly on the first try.
Tagged
Keep reading
Takina · · 6 min read How to Break Up Long Tasks in JavaScript
Long tasks block the main thread for 50ms or more and make pages feel frozen. How to split them with yielding, scheduler.yield(), postTask, and workers.
Takina · · 5 min read Microtasks vs Macrotasks in JavaScript, Explained
Microtasks (promise callbacks) run before the next macrotask (timers, events). How the two queues are ordered, and why it matters for your code.
Takina · · 5 min read OffscreenCanvas API Explained
OffscreenCanvas lets you render canvas graphics off the main thread, in a web worker, so heavy drawing work stops blocking scrolling, input, and animation.