urlpattern
js/globals/urlpattern.ts
URLPattern global (WHATWG URL Pattern API)
URLPattern lets you declare a pattern for URL matching and then test or
match URLs against it. The release baseline is routing-oriented: matching
request URLs by protocol, hostname, path, search, and hash, and extracting
named groups from path and host patterns such as /users/:id.
The implementation follows the broad WHATWG URL Pattern Standard shape
(https://urlpattern.spec.whatwg.org/) but is not strict tokenizer parity. It
processes patterns per URL component (protocol, username, password, hostname,
port, pathname, search, hash) so you can match any combination of URL parts.
Coverage locks object and string constructors, baseURL resolution, named
parameters, wildcard and regexp groups, repeat modifiers, escaped literals,
hasRegExpGroups, ignoreCase, and percent-encoding boundaries used by
routing code.
Architecture: tokenize → compile → match
Pattern processing is a two-stage pipeline:
Stage 1 — Tokenize (_tokenize):
The pattern string is scanned character by character into a flat token
array. Token types:
- T_TEXT — literal text to match verbatim
- T_ESCAPED — a \x-escaped literal character
- T_NAME — a :name named parameter
- T_PATTERN — a (regex) custom regex group
- T_ASTERISK — a bare * wildcard
- T_OPEN / T_CLOSE — { / } non-capturing group delimiters
- T_MODIFIER — ?, + (follows a group or name)
- T_END — sentinel
Stage 2 — Compile (_compileTokens):
Tokens are converted to a RegExp and a parallel keys array:
- T_TEXT → _escapeRe(value) (escaped literal)
- T_NAME → (pattern)modifier where pattern defaults to
[^delimiter]+? (matches everything except the delimiter, e.g. /
for pathname) unless a T_PATTERN immediately follows the name
- T_PATTERN → (regex)modifier with an auto-generated numeric key
- T_ASTERISK → (.*) with an auto-generated numeric key
- T_OPEN group → (?:inner)modifier (non-capturing group in regex)
The keys array parallels the regex capturing groups: keys[i].name is
the name (or auto-generated index string) of capture group i+1.
Per-component options
Each URL component is compiled with different options.delimiter:
- pathname uses '/' as the delimiter, so :name matches a single
path segment by default (stops at the next /)
- hostname uses '.' as the delimiter, so :sub matches a single
subdomain label
- other components (search, hash, protocol, etc.) have no delimiter,
so :name matches any non-empty run of characters
String constructor input
When URLPattern is constructed with a string (e.g.
new URLPattern('https://example.com/users/:id')), the string is parsed
into its URL components first using _parsePatternInitString(). This function
finds the scheme, authority, path, search, and hash sections by scanning
for the structural characters (:, //, /, ?, #) while respecting
(...) and {...} groups that may contain those characters. A baseURL
option can provide defaults for any missing components.
exec() and test()
exec(input) tries to match input (a URL string or URL object) against
all compiled components. If all match, it returns a result object with
per-component { input, groups } values. test(input) is just
exec(input) !== null.
Canonicalization
Literal pattern text is canonicalized on construction, mirroring the spec's
per-component encoding callbacks: text in username, password, pathname,
search, and hash patterns is percent-encoded with that component's encode
set, and literal hostname patterns are canonicalized through the URL parser
(Unicode labels become punycode). The component getters return the
canonical pattern string, so new URLPattern({ pathname: '/café' }).pathname
reads back as '/caf%C3%A9', and (.*) groups read back as * wildcards.
Bodies of custom (regexp) groups are used verbatim — no encoding is
applied inside them.
Beyond matching
generate(component, groups) runs a pattern in reverse, substituting
:name groups to produce a concrete component string. The static
URLPattern.compareComponent(component, left, right) orders two patterns
by routing precedence so routers can sort more-specific routes first.
What is NOT implemented
- Strict WHATWG tokenizer state machine parity. This uses a hand-rolled scanner that covers the routing syntax but may differ in edge cases.
// URLPattern is available via globalThis
// Object form (most precise):
const p = new URLPattern({ pathname: '/users/:id' });
p.test('https://example.com/users/42'); // true
p.exec('https://example.com/users/42');
// → { pathname: { input: '/users/42', groups: { id: '42' } }, ... }
// String form:
const q = new URLPattern('https://example.com/users/:id');
q.test('https://example.com/users/42'); // true
// Supported pattern syntax (per component):
// :name — named param, matches non-delimiter chars by default
// :name(regex) — named param with custom regex
// (regex) — unnamed group with custom regex
// * — wildcard, matches anything
// {group} — non-capturing group
// ? + * — modifiers after :name, (regex), {group}, or *
// \x — literal escape
Classes
class URLPattern {
WHATWG URLPattern implementation for matching URLs by component.
Patterns may be supplied as a URL-like string or as an object with per-part
patterns. Missing components default to the * wildcard, so
new URLPattern({ pathname: '/users/:id' }) matches that path on any
protocol, host, and port. Installed on globalThis — no import is needed.
Use test() for a boolean check, exec() to extract named groups,
generate() to build a component string back from a pattern, and the
static compareComponent() to sort patterns by routing precedence.
const pattern = new URLPattern({ pathname: '/users/:id' });
pattern.test('https://example.com/users/42'); // true
const match = pattern.exec('https://example.com/users/42');
match?.pathname.groups.id; // "42"
Constructors
constructor(
input?: string | URLPatternInit,
baseURLOrOptions?: string | URLPatternOptions,
options?: URLPatternOptions
)
Create a URLPattern from string, component-object, or omitted input.
String input is split into URL components while respecting pattern groups.
baseURL supplies defaults for relative string patterns and component
dictionaries. Object input uses the provided component patterns directly.
Missing or undefined input creates an all-wildcard pattern. Pass
{ ignoreCase: true } as the options object to match ASCII and Unicode
letters without case sensitivity.
Throws a TypeError if the input is neither a string nor an object, or
if a component pattern fails to parse — unmatched (, an empty ()
group, a : with no valid parameter name, or an invalid literal
hostname.
const pattern = new URLPattern('/files/:name', 'https://example.com', {
ignoreCase: true,
});
pattern.hostname; // "example.com"
Getters
get protocol()
Protocol pattern without the trailing colon.
new URLPattern({ protocol: 'https' }).protocol; // "https"
get username()
Username pattern.
new URLPattern({ username: '*' }).username; // "*"
get password()
Password pattern.
new URLPattern({ password: '*' }).password; // "*"
get hostname()
Hostname pattern in canonical form.
Hostname named parameters stop at "." by default. Literal hostnames are canonicalized through the URL parser, so Unicode labels read back as punycode.
new URLPattern({ hostname: ':sub.example.com' }).hostname; // ":sub.example.com"
new URLPattern({ hostname: 'café.example' }).hostname; // "xn--caf-dma.example"
get port()
Port pattern.
new URLPattern({ port: '8080' }).port; // "8080"
get pathname()
Pathname pattern in canonical form.
Pathname named parameters stop at "/" by default. Literal text is
percent-encoded, so /café reads back as /caf%C3%A9.
new URLPattern({ pathname: '/users/:id' }).pathname; // "/users/:id"
get search()
Search pattern without leading question mark.
new URLPattern({ search: 'q=:term' }).search; // "q=:term"
get hash()
Hash pattern without leading hash.
new URLPattern({ hash: 'top' }).hash; // "top"
get hasRegExpGroups(): boolean
True when any component contains an explicit regexp group.
Both unnamed (pattern) groups and named params with a custom regexp
(:name(pattern)) count. Plain :name params and * wildcards do not,
even though they also compile to capturing groups internally.
new URLPattern({ pathname: '/:id(\\d+)' }).hasRegExpGroups; // true
new URLPattern({ pathname: '/:id' }).hasRegExpGroups; // false
Methods
test(input: string | {
href: string;
} | URLPatternInit, baseURL?: string): boolean
Return true when input matches every URL component pattern.
Invalid string or href inputs return false. Object inputs are interpreted as
already-split URLPatternInit component values. When input is a relative
URL string, the optional baseURL argument resolves it to an absolute URL
before matching; an input that cannot be parsed against baseURL returns
false rather than throwing.
const pattern = new URLPattern({ pathname: '/users/:id' });
pattern.test('https://example.com/users/42'); // true
generate(component: string, groups: Record<string, unknown> = {}): string
Generate one URL component from literal text and simple named groups.
The current support covers the common routing subset used by the WPT
generation fixture: text tokens, escaped literals, and :name groups.
Pathname group values are percent-encoded when the pattern's protocol is
a special scheme (http, https, ws, wss, ftp, file) or a wildcard, and
generated hostnames are canonicalized through the URL parser.
Throws a TypeError for an invalid component name, a non-object groups
argument, a :name group with no entry in groups, a pathname group
value containing /, or a pattern using unsupported syntax — wildcards,
regexp groups, and optional or repeated groups.
new URLPattern({ pathname: '/users/:id' }).generate('pathname', { id: '42' });
// "/users/42"
exec(input: string | {
href: string;
} | URLPatternInit, baseURL?: string): {
inputs: any[];
protocol: URLPatternComponentResult;
username: URLPatternComponentResult;
password: URLPatternComponentResult;
hostname: URLPatternComponentResult;
port: URLPatternComponentResult;
pathname: URLPatternComponentResult;
search: URLPatternComponentResult;
hash: URLPatternComponentResult;
} | null
Match input and return per-component inputs and capture groups.
Returns null if parsing fails or any component does not match. A relative
input string is resolved against the optional baseURL argument first.
On a successful match the returned inputs array echoes the call shape —
[input], or [input, baseURL] when a base URL was supplied — and each
component field carries the matched substring plus its named capture groups.
const pattern = new URLPattern({ pathname: '/users/:id' });
const match = pattern.exec('https://example.com/users/42');
match?.pathname.groups.id; // "42"
Static Methods
static compareComponent(component: string, left: URLPattern, right: URLPattern): number
Compare two URLPattern component patterns for routing precedence.
Returns a negative number when left sorts before right, positive when
it sorts after, and 0 when the patterns are equivalent. Parameter names
do not affect ordering. Literal text sorts above regexp groups, named
groups, and wildcards; group modifiers sort as *, ?, +, then no
modifier.
Throws a TypeError if component is not a URL component name or either
argument is not a URLPattern instance.
URLPattern.compareComponent(
'pathname',
new URLPattern({ pathname: '/users/:id' }),
new URLPattern({ pathname: '/users/*' }),
); // 1