URLPattern is now a practical baseline primitive for web routing: it gives browser code and workers one shared way to test a URL and extract named parameters without hand-written regular expressions or a framework-only matcher. For teams building service workers, edge-style request handlers, or progressively enhanced navigation, the useful move is to make patterns small, explicit, well-tested, and isolated behind a routing boundary.
TL;DR
The URL Pattern API standardises the matching syntax that many routers have independently implemented for years. A URLPattern can match a whole URL or individual components such as pathname, hostname, and search, then return named captures. It became Baseline Newly Available in September 2025, which makes it relevant for new browser-first code in 2026—provided an application still has a fallback plan for older client fleets.
Contents
- What problem does URLPattern solve?
- Why is URLPattern relevant now?
- How do you match and extract route parameters?
- Where does URLPattern fit in an application?
- How should teams design patterns safely?
- What are the compatibility and migration rules?
- FAQ
What problem does URLPattern solve?
Routing is mostly a decision about a URL: does this request belong to a product page, an account area, a locale, or an API endpoint? Historically, the same application often answered that question with three unrelated mechanisms: framework routes on the server, a different router in the client, and ad-hoc string checks in a service worker.
That repetition creates subtle bugs. One implementation decodes a segment before matching; another allows a trailing slash; a third accidentally treats a query string as part of the pathname. The URL Pattern Standard defines a platform-level matcher whose components mirror a URL: protocol, hostname, port, pathname, search, and hash.
The point is not to replace a full application router. Framework routers still own rendering, data loading, layouts, redirects, and error boundaries. URLPattern is the smaller primitive underneath: a predictable test plus structured captures. It is especially handy where installing a router would be excessive.
This is the same direction as the platform features covered in the Navigation API and Declarative Shadow DOM: move common infrastructure into interoperable browser capabilities, while keeping application policy in the app.
Why is URLPattern relevant now?
The API is not new in the abstract—Chrome documented its initial implementation years ago in “URLPattern brings routing to the web platform”. What changed is reach. MDN now classifies URLPattern as Baseline Newly Available from September 2025, meaning current major-browser versions have converged enough for it to be a reasonable option in a deliberately scoped feature.
That matters for two kinds of work:
- Service workers and web workers. The interface is available in workers, so the same matcher can classify requests without copying route logic into fragile string operations.
- Small browser utilities. A documentation site, embedded widget, or analytics filter can recognise a route without importing a client router just to parse
/:slug. - Shared boundary code. Where code runs in both a modern server runtime and a browser, a single declarative grammar is clearer than a pile of
split('/')calls.
It also complements—not duplicates—the Navigation API. Navigation tells an application that a navigation is happening and offers a lifecycle to intercept it. URLPattern answers whether that navigation targets a route the application cares about. Keeping those jobs separate makes both easier to test.
How do you match and extract route parameters?
Start with component-object syntax. It is more readable than a one-line URL pattern and prevents a route from silently matching the wrong host or protocol.
const productRoute = new URLPattern({
protocol: 'https',
hostname: 'shop.example',
pathname: '/products/:id(\\d+)',
});
const match = productRoute.exec('https://shop.example/products/742');
if (match) {
console.log(match.pathname.groups.id); // "742"
}exec() returns null for a non-match. On a match, each URL component has a result object and its groups object holds named captures. Use test() if all that matters is true or false:
const publicAsset = new URLPattern({
pathname: '/assets/*',
});
if (publicAsset.test(new URL('/assets/logo.svg', location.origin))) {
// Cache or serve this class of request.
}Patterns support named groups (:id), wildcards (*), optional groups, and regular-expression groups. The standard’s examples show how each component gets its own separator rules; a pathname capture stops at /, whereas hostname captures have different boundaries. That component awareness is a big improvement over applying one broad regular expression to href.
Do not reach for regex in a route unless the constraint is genuinely part of the route contract. :id(\\d+) is valuable because an endpoint may only accept numeric IDs. A complex regex that tries to validate every possible slug usually hides policy that belongs in validation after the route has matched.
Where does URLPattern fit in an application?
The strongest use is at boundaries where a URL must be classified before framework code runs. A service worker can reserve API calls for the network while handling static assets and navigation requests differently:
const api = new URLPattern({ pathname: '/api/:resource/*' });
const asset = new URLPattern({ pathname: '/assets/*' });
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
if (api.test(url)) {
event.respondWith(fetch(event.request));
return;
}
if (asset.test(url)) {
event.respondWith(caches.match(event.request).then(hit => hit ?? fetch(event.request)));
}
});This is deliberately classification, not authorization. A pattern that recognises /api/admin/* must never be treated as evidence that the caller is an administrator. Authentication and authorization remain server-side checks.
Another useful pattern is an explicit route table for browser behaviour:
const routes = [
{ pattern: new URLPattern({ pathname: '/docs/:section/:page?' }), name: 'docs' },
{ pattern: new URLPattern({ pathname: '/pricing' }), name: 'pricing' },
];
function findRoute(url) {
return routes.find(({ pattern }) => pattern.test(url));
}An adapter like findRoute() is important. It keeps the rest of the product from depending directly on an API whose support needs may differ across embedded webviews. It also makes future routing changes local, much like import attributes keep a module’s data-type expectation explicit at its boundary.
How should teams design patterns safely?
Treat patterns as an allowlist of recognised shapes, not as an attempt to parse the entire internet. A few rules avoid most surprises:
- Constrain the component that matters. If code should only run on a known origin, include
protocolandhostname; a pathname-only pattern can match an unexpected input origin. - Use the
URLconstructor at input boundaries. Parse external strings before matching. This separates URL parsing errors from routing decisions and avoids assumptions about relative URLs. - Order broad routes last.
/products/:idshould precede/products/*in a table, just as it would in a framework router. - Keep matching separate from decoding and validation. A group is an extracted string, not a verified database key or an HTML-safe value.
- Test non-matches. For every accepted route, add tests for a different host, malformed identifier, unexpected prefix, and trailing shape that must be rejected.
The standard also supports a shorthand form, such as new URLPattern('/blog/:slug', location.origin). It is useful for tiny local cases. For shared infrastructure, the object form documents exactly which components are constrained and is less likely to surprise a reviewer.
What are the compatibility and migration rules?
Baseline is a deployment signal, not permission to delete all fallbacks. A public site may still have older devices, enterprise browsers, embedded webviews, or a support contract that makes a native-only dependency inappropriate.
Feature-detect at the narrowest point where it is needed:
export function matchesProduct(url) {
if ('URLPattern' in globalThis) {
return new URLPattern({ pathname: '/products/:id(\\d+)' }).test(url);
}
// Small, well-tested fallback for the one route this feature needs.
return /^\/products\/\d+$/.test(new URL(url).pathname);
}For a large router, keep using the framework’s established matcher until its supported environments can use the platform capability without a compatibility cost. Native APIs are most compelling where they remove one focused dependency or duplicated utility—not where they trigger a risky rewrite.
Measure before claiming a performance win. URLPattern improves consistency and readability; it is not automatically faster than a mature framework’s precompiled route matcher. The right metric is fewer divergent route definitions and fewer classification bugs.
FAQ
Does URLPattern replace React Router, Next.js, or another framework router?
No. It only matches URLs and exposes captures. Framework routers still provide rendering, nested layouts, loading, and application conventions. Use it for platform-level or lightweight classification, or behind an adapter where it fits.
Can URLPattern secure an API route?
No. It can select code based on a request shape, but it cannot authenticate a user or authorise access. Enforce those checks on the server after routing.
Should a team use URLPattern everywhere immediately?
No. Start with a worker, utility, or new isolated feature. Check real browser and webview traffic, feature-detect where needed, and retain your existing router where it already solves the broader problem well.
What is the main advantage over a regular expression?
It understands URL components and returns named captures in a standard API. That makes intent clearer, reduces accidental matching across URL parts, and lets browser and worker code share the same route grammar.