{placeholders}
in the page’s slug, and every URL that fits the pattern renders that page with
the URL’s segments handed to your template as route_params.
One “guided course” page can then serve
/app/guided-courses/foundations/module-1,
/app/guided-courses/advanced/lesson-5, and every other course and module.
Prefer a wildcard route over a query string. For product, course, article
and category detail pages,
/shop/product/blue-widget beats
/shop/product?id=42 on every axis that matters — see
Use paths, not ?id= below.How a URL is matched
When a request arrives, the runtime resolves it in three steps and stops at the first hit:- Exact slug. A page whose slug is literally the requested path.
- Wildcard patterns. Pages whose slug contains
{…}, tried longest URL prefix first. - Folder index.
/<path>and/<path>/fall back to a page saved as<path>/index. This is a last resort, soecom/{slug}still wins overecom/indexfor/ecom/cart.
- Its static prefix — everything before the first
{— is exactly the URL prefix being tested. - Its number of placeholders equals the number of segments left over.
app will not catch /app/anything; only app/{slug} will.
Setting up a wildcard route
1. Create the page
Give the page a slug that contains the placeholders, e.g.- Fixed part:
app/guided-courses - Dynamic parts:
{course}and{module}
2. What matches
Rules and limits
Placeholders must all be trailing. The static prefix stops at the first{, so any literal segment written after a placeholder is ignored when
matching:
To keep a literal segment in the middle, put it in the fixed part instead:
blog/tech/posts/{id}.
A slug cannot start with a placeholder. Matching always tests at least one
literal leading segment, so a slug like {hl}/about never matches any URL.
Start the pattern with a real segment (docs/{topic}).
Sibling patterns are disambiguated by prefix. blog/{slug} and
blog/category/{slug} can coexist: /blog/hello picks the first,
/blog/category/news picks the second, because each pattern’s static prefix
must equal the URL prefix exactly.
Offline and deleted pages are skipped, and at most 50 candidate patterns
are considered per prefix.
Wildcard pages are never listed in sitemap.xml or llms.txt — a
{param} slug stands for many URLs, not one. Link to concrete URLs from a
listing page you do publish.
Reading the segments
Two values are available once a wildcard route matches:route_params— an object keyed by your placeholder names, e.g.{ course: 'foundations', module: 'module-1' }.route_slug— the whole remainder as one string, e.g.foundations/module-1.
null when the page was reached by an exact slug, the domain index,
or the folder-index fallback.
The keys of
route_params are always your placeholder names. There is no
segment_0 / segment_1 — name the placeholder whatever you want to read
({slug} → route_params.slug).In a template
In a backend script
route_params arrives on the request object:
Guarding
On a non-wildcard URLroute_params is null, so guard before reading it:
Use paths, not ?id=
For any page that renders one of many records — a product, a course, a
blog post, a category — put the identifier in the path via a wildcard
route rather than in a query string.
Query strings still make sense for things that modify a view rather than
identify it:
?page=2, ?sort=price, ?hl=en, and UTM tags.
Product detail pages
Build the page once with a placeholder:-
Page slug:
shop/product/{code} -
Template:
getProduct() accepts a product id or code, so the segment can be either.
Link to it from your catalog with the productUrl filter, which emits
/<base>/<slug>-<id>:
/shop/product/blue-widget-42, so pair it with the slug
shop/product/{handle} and split the trailing id in a backend script:
Course detail pages
-
Page slug:
app/guided-course/{course} -
Template:
getCourseBySlug() also falls back to the URL on its own when you call it
with no argument: it reads ?slug=, then route_params.course, then
route_slug. Passing the value explicitly is clearer, and required when your
placeholder is named anything other than course.
Examples
One placeholder — record by slug
- Page slug:
app/guided-course/{slug} - URLs:
/app/guided-course/foundations,/app/guided-course/advanced - In template:
route_params.slug(orroute_slug, same value here)
Two placeholders — course + module
- Page slug:
app/guided-courses/{course}/{module} - URLs:
/app/guided-courses/foundations/module-1 - In template:
route_params.course,route_params.module
Three placeholders
- Page slug:
learn/{topic}/{level}/{lesson} - URL:
/learn/javascript/beginner/01-variables - In template:
route_params.topic,route_params.level,route_params.lesson
Summary
Use
{name} in the slug for each dynamic part, then read
route_params.name (or route_slug) in the template or
request.route_params in a backend script.
Related
- Products and shipping —
productUrl,getProduct, and the product fields to render. - Internationalization (i18n) —
route_params.hlandroute_params.localeare the highest-priority locale hints, ahead of?hl=. - Rendering pipeline — where route resolution sits relative to the template engines.