KosmoJS supports three parameter types, using the same syntax for both API routes and client pages:
| Syntax | Type | Matches |
|---|---|---|
[id] | Required | Exactly one segment |
{id} | Optional | One segment or nothing |
{...path} | Splat | Any number of segments |
[] Required Parameters β
users/[id]/index.ts β /users/123, /users/abcThe parameter name becomes the key in ctx.validated.params (or your framework's equivalent). [id] gives you params.id, [userId] gives you params.userId.
{} Optional Parameters β
users/{id}/index.ts β /users and /users/123Useful for combining list and detail views in a single handler, branching on whether the parameter is present.
Important: optional parameters must not be followed by required parameters.
users/{section}/{subsection} β
users/{optional}/[required] βWatch Out for Ambiguous Paths β
Optional parameters followed by static segments can cause unexpected 404s:
properties/{city}/filters/index.tsxVisiting /properties/filters: the router matches {city} = "filters", then expects another /filters segment - which isn't there. Result: 404.
Fix it by adding an explicit static route:
properties/
βββ filters/index.tsx β /properties/filters
βββ {city}/
βββ filters/index.tsx β /properties/NY/filtersStatic routes always take priority over dynamic ones.
Required vs Optional - a Subtlety β
[id] technically means "required at this URL position", but a sibling index file changes that:
careers/
βββ index.tsx β /careers (fallback when no id)
βββ [jobId]/
βββ index.tsx β /careers/123When a parent index exists, [jobId] is effectively optional - there's a fallback to render. In this case, {jobId} communicates intent more clearly and both notations work identically.
{...} Splat Parameters β
docs/{...path}/index.ts β /docs/getting-started
β /docs/api/reference
β /docs/guides/deployment/productionThe matched segments are provided as an array - useful for doc sites, file browsers, or anything with arbitrarily nested paths.
π Mixed Segments β
Segments can combine static text with parameters:
products/[category].html β /products/electronics.html
profiles/[id]-[data].json β /profiles/1-posts.json
files/[name].[ext] β /files/document.pdfMixed segments work fully for backend routes (Koa/Hono). Frontend support varies:
- Vue Router - full support
- MDX - full support
- React Router -
.extsuffix only - SolidJS Router - not supported
Prefer simple segments for frontend routes.
β‘ Power Syntax β
For advanced cases, KosmoJS passes path-to-regexp v8 patterns through directly. The rule: if the param name contains non-alphanumeric characters, it's treated as a raw pattern.
This unlocks things like optional static parts:
products/{:category.html}/productsβ (no category, no.html)/products/electronics.htmlβ/products/electronicsβ (.htmlrequired when category is present)
More examples:
book{-:id}-info β /book-info or /book-123-info
locale{-:lang{-:country}} β /locale, /locale-en, /locale-en-US
api/{v:version}/users β /api/users or /api/v2/usersUse power syntax carefully - read the path-to-regexp docs before applying it to production routes.