Skip to content

Wildcard Routing

Wildcard routing lets you create CMS pages that match dynamic URL patterns. Instead of creating individual pages for every product or category, you define a template page with wildcard segments.

Path patterns

Create a page in the CMS with * in the path:

CMS PathMatchesDoes not match
/products/*/products/iphone, /products/macbook/pro/14/products (no segment after)
/category/*/shirts/category/men/shirts/category/men/pants
/*/anything, /deep/nested/path/ (root)

Matching rules

  • Exact match wins — if both /products/featured and /products/* exist, a request for /products/featured uses the exact match.
  • Trailing * is a catch-all — matches one or more remaining segments. /products/* matches /products/a/b/c.
  • Non-trailing * matches one segment/category/*/shirts requires exactly that structure.
  • Most specific pattern wins — if multiple wildcards match, the one with more literal segments is used.

Creating wildcard pages

  1. Go to Content and click + New Content
  2. Enter a path with * segments, e.g. /products/*
  3. The page is automatically flagged as is_wildcard in the database
  4. Add components, publish as normal

Matched segments

When a wildcard page matches a request, the delivery API returns _segments with the captured values:

{
"componentsAboveFold": [...],
"componentsBelowFold": [...],
"layout": { ... },
"_matchedPattern": "/products/*",
"_segments": {
"0": "iphone-17"
}
}

For multi-wildcard patterns like /category/*/shirts/*:

{
"_matchedPattern": "/category/*/shirts/*",
"_segments": {
"0": "men",
"1": "blue"
}
}

Segments are indexed by position ("0", "1", etc.).

Delivery API behavior

The GET /api/v1/pages/by-path/* endpoint follows this resolution order:

  1. KV cache hit — return cached data immediately
  2. Exact path match — query D1 for published page with exact path
  3. Wildcard match — query all is_wildcard = 1 pages, test each pattern, pick the most specific match
  4. Layout-only fallback — if no page or wildcard matches, return { _notFound: true, layout: {...} }
  5. 404 — if no layout exists either

Caching

Wildcard matches are cached under the concrete requested path. So after the first request to /products/iphone-17, subsequent requests hit KV cache directly without wildcard resolution.

Database schema

The pages table has an is_wildcard column:

ALTER TABLE pages ADD COLUMN is_wildcard INTEGER NOT NULL DEFAULT 0;

This is set automatically when a page path contains *. The incremental migration in migrate.ts adds this column on first startup.