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 Path | Matches | Does 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/featuredand/products/*exist, a request for/products/featureduses 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/*/shirtsrequires exactly that structure. - Most specific pattern wins — if multiple wildcards match, the one with more literal segments is used.
Creating wildcard pages
- Go to Content and click + New Content
- Enter a path with
*segments, e.g./products/* - The page is automatically flagged as
is_wildcardin the database - 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:
- KV cache hit — return cached data immediately
- Exact path match — query D1 for published page with exact path
- Wildcard match — query all
is_wildcard = 1pages, test each pattern, pick the most specific match - Layout-only fallback — if no page or wildcard matches, return
{ _notFound: true, layout: {...} } - 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.