Matching URL patterns

URLPattern

Baseline newly available
  • Chrome95
  • Edge95
  • Firefox142
  • Safari26

Features it needs

These libraries compile a route string such as "/users/:id" into a regular expression and hand back the named groups when it matches. URLPattern is that same idea built in, with one extra: it matches every part of a URL, so protocol, hostname and search can take part in the pattern rather than only the path. A match returns an object keyed by URL part, each with its own groups.

When this applies

Matching a URL against a route pattern and reading the named parameters.

The native approach

const pattern = new URLPattern({ pathname: "/users/:id/posts/:postId" });

pattern.test("https://example.com/users/42/posts/7"); // true

const match = pattern.exec("https://example.com/users/42/posts/7");
match.pathname.groups; // { id: "42", postId: "7" }

// Other parts of the URL can take part too.
new URLPattern({ hostname: ":tenant.example.com" })
  .exec("https://acme.example.com/")
  .hostname.groups; // { tenant: "acme" }

MDN reference

When the dependency is still right

An answer that always says "the platform covers it" is worse than no answer. These are the cases where this one does not hold.

  • You build URLs from the pattern as well as matching them. path-to-regexp's compile() turns "/users/:id" plus params back into a path, and URLPattern only goes in the matching direction.
  • You are replacing the matcher inside a router you do not control. Express and React Router depend on path-to-regexp directly, so it stays in the tree regardless of what your own code uses.
  • Your routes rely on path-to-regexp syntax that does not carry over, such as its custom match patterns or its handling of unnamed groups. The two syntaxes are close enough to look interchangeable and different enough to break quietly.
  • The code runs on a server runtime that has no URLPattern global, which is the case for Node before it shipped one.

Signs it was hand-rolled

No package is involved in any of these, so nothing would match in a package.json. If the code looks like one of them, this rule applies anyway, and the conditions above still decide.

  • a hand-written regex per route with named capture groups, and a loop testing each against the path
  • pathname.split("/") compared segment by segment to work out which route matched and pull out an id

Packages this covers