· 5 min read
URLPattern: match and parse URLs natively in JavaScript
URLPattern matches URLs against route patterns like /books/:id and returns named groups. Learn the syntax, gotchas and where it is supported.

URLPattern is a built-in JavaScript API that matches URLs against patterns like /books/:id and pulls out the named parts for you. It is the same route syntax most routers already use, now shipped in the platform, so you can match and parse URLs without a routing library or a hand-written regex. It became Baseline in September 2025 and is available as a global in Node.js 24.
If you have ever written url.pathname.split("/")[2] and hoped nobody changes the route, this is the API to learn.
What is URLPattern?
A URL pattern is a template for a URL, where some parts are fixed text and others are placeholders. URLPattern compiles that template once, then tells you whether a given URL matches and what each placeholder captured.
const pattern = new URLPattern({ pathname: "/books/:id" });
pattern.test("https://example.com/books/42"); // true
const match = pattern.exec("https://example.com/books/42?x=1");
match.pathname.groups; // { id: "42" }
A pattern is made of components, one for each part of a URL: protocol, username, password, hostname, port, pathname, search and hash. Any component you leave out defaults to *, which matches anything. That is why the pattern above matches any host and ignores the query string.
There are two methods. test() returns a boolean. exec() returns null on no match, or an object with one entry per component, each holding the original input and the captured groups.
How does the pattern syntax work?
The syntax comes from the path-to-regexp library that Express and many other routers are built on, so it will look familiar.
- Named groups:
:namecaptures a segment. Inpathnameit matches everything up to the next/. Inhostnameit stops at the next.. - Wildcards:
*matches any run of characters, including slashes, and is captured as an unnamed group numbered from0. - Regex groups:
:id(\\d+)restricts what a named group accepts using a regular expression. - Modifiers:
?makes the group before it optional,+means one or more,*means zero or more. - Braces:
{...}group a piece of the pattern without capturing it, so a modifier can apply to the whole piece.
Here they are in practice:
const docs = new URLPattern({ pathname: "/docs/:section/:page?" });
docs.exec("https://a.com/docs/api").pathname.groups;
// { section: "api", page: undefined }
docs.exec("https://a.com/docs/api/fetch").pathname.groups;
// { section: "api", page: "fetch" }
const files = new URLPattern({ pathname: "/files/*" });
files.exec("https://a.com/files/a/b/c.txt").pathname.groups;
// { "0": "a/b/c.txt" }
const users = new URLPattern({ pathname: "/users/:id(\\d+)" });
users.test("https://a.com/users/7"); // true
users.test("https://a.com/users/abc"); // false
Note the double backslash. The pattern is a JavaScript string, so \\d in your source becomes \d in the pattern. If you build the literal part of a pattern from a variable, the same escaping concerns apply as with building a regex from user input, so keep variable text out of regex groups.
Hostnames work the same way, which is handy for subdomain tenants:
const tenant = new URLPattern({ hostname: ":tenant.example.com" });
tenant.exec("https://acme.example.com/x").hostname.groups; // { tenant: "acme" }
How do you create a URLPattern?
The constructor takes one of two shapes.
| Form | Example | Notes |
|---|---|---|
| Object of components | new URLPattern({ pathname: "/books/:id" }) | Clearest; unspecified parts match anything |
| Full pattern string | new URLPattern("https://*.example.com/books/:id") | Every part comes from the string |
| Relative string plus base | new URLPattern("/books/:id", "https://example.com") | Host and protocol come from the base |
A relative string without a base throws a TypeError. Passing a base URL together with an object or an absolute pattern string is also a TypeError.
Both forms accept a final options object. Right now it has one setting, ignoreCase. Matching is case-sensitive by default:
new URLPattern({ pathname: "/About" }).test("https://a.com/about"); // false
new URLPattern({ pathname: "/About" }, { ignoreCase: true }).test("https://a.com/about"); // true
The compiled pattern also exposes each component as a read-only property (pattern.pathname, pattern.hostname, and so on), plus hasRegExpGroups, which is true when any component uses a custom regex group.
What trips people up?
A few behaviors catch people the first time.
- Trailing slashes matter.
/books/:iddoes not match/books/42/. If you want both, say so:/books{/}?matches/booksand/books/. :namedoes not cross slashes. Use:path+or*when a placeholder should span several segments./a/:x+against/a/1/2/3gives{ x: "1/2/3" }.- The query string is its own component. Omitting
searchmeans "any query", not "no query". Read query values withURLSearchParamsafter the match, sincesearchpatterns match the raw string and parameter order is not guaranteed. - Inputs can be objects too.
pattern.test({ pathname: "/books/9" })works, which is useful when you only have a path and no full URL.
When should you use URLPattern?
It fits anywhere you would otherwise reach for a small router or a regex over location.pathname: service worker fetch handlers, edge and serverless functions, API handlers in plain Node, and client-side navigation code. A minimal router is a loop:
const routes = [
{ pattern: new URLPattern({ pathname: "/api/users/:id(\\d+)" }), handler: ({ id }) => `user ${id}` },
{ pattern: new URLPattern({ pathname: "/api/posts/:slug" }), handler: ({ slug }) => `post ${slug}` },
];
function route(url) {
for (const { pattern, handler } of routes) {
const match = pattern.exec(url);
if (match) return handler(match.pathname.groups);
}
return "404";
}
route("https://example.com/api/users/12"); // "user 12"
route("https://example.com/api/users/me"); // "404"
route("https://example.com/api/posts/hello-world?draft=1"); // "post hello-world"
This checks routes in order, so put more specific patterns first. For a handful of routes that is plenty. For hundreds, a router that builds a prefix tree will be faster than testing each pattern in turn, and that is where a framework still earns its place.
Support looks like this:
| Environment | Support |
|---|---|
| Chrome and Edge | 95 and later |
| Firefox | 142 and later |
| Safari | 26 and later |
| Node.js | Global from 24; not global in Node.js 22 |
For older browsers, feature-detect with "URLPattern" in globalThis and load the urlpattern-polyfill package as a fallback.
Key takeaways
URLPatternmatches URLs against route-style patterns and returns named groups for each part of the URL.- Syntax:
:namefor segments,*for anything,(regex)to restrict,?+*as modifiers,{}for grouping. - Omitted components match anything, and trailing slashes are significant.
- It is Baseline since September 2025 and a global in Node.js 24.
- Use it for small routers, service workers and edge handlers; keep a framework router for large route tables.
FAQ
Is URLPattern faster than a regular expression?
Not meaningfully. The spec defines each component in terms of a regular expression, so the main gain is readability and correct handling of each URL part, not speed.
Does URLPattern parse query parameters?
No. It matches the search component as a string. Use new URL(input).searchParams to read individual parameters after a match.
Does a pattern match a trailing slash?
No. /books/:id rejects /books/42/. Add an optional slash with {/}? at the end of the pattern if you want to accept both.