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

// 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