routes
The router does one thing: binds a path to what is shown there. Data and write logic live in the tables (views and forms); a route only references them by name. A page route with path "/" (the site index) is required.
page — an HTML page: path, hbs (the template file, with the .hbs extension), optional view for data. Which skeleton wraps the page is written in the template itself — it opens with {{#> _layout}} (see Templates).
A multi-block page (a homepage, a landing) declares views instead of view — several named views on one page, executed concurrently: "views": { "fresh": "home_items", "faq": "faq_list" }. Each key holds its result in the template context — {{#each fresh.rows}}, {{faq.total}}, for a single view {{key.row.title}}. The two keys cannot be mixed on one route, and params is reserved.
api, GET — serves view data: path, view, format (json | html). Always raw values.
api, method POST — the ADDRESS of a public form: { "method": "POST", "path": "/api/feedback", "form": "contact" }. The form itself is declared in its table (tables.<t>.forms):
| form key | description |
|---|---|
| fields | what a client may submit: name, widget, required, max_length |
| access | admin (default) | user | public. Only a public form is accepted at a public address — the engine checks the entry-point status at runtime, the config cannot bypass it |
| honeypot | bot-trap field name; a non-empty value means silent success, nothing stored |
| auto_fields | server-side values: column → "now" (the intake timestamp) |
| on_success / on_error | redirect to a path, or render an hbs template |
Intake is rate-limited by the engine itself — there is no config switch to weaken it.
Real wiring from this site — the page, the JSON endpoint, the contact form address and the form it points to:
"routes": [
{ "type": "page", "path": "/docs/{grp}", "hbs": "docs/group.hbs", "view": "doc_list" },
{ "type": "api", "path": "/api/docs", "view": "doc_list", "format": "json" },
{ "type": "api", "method": "POST", "path": "/api/feedback", "form": "contact" }
]
"tables": { "feedback": { "forms": {
"contact": {
"access": "public",
"fields": [ { "name": "message", "required": true, "max_length": 5000 } ],
"honeypot": "website",
"auto_fields": { "created_at": "now" },
"on_success": { "redirect": "/thanks" },
"on_error": { "hbs": "errors/400.hbs" }
}
} } }Paths on or under /cms, /mcp and /static/wasm are reserved by the engine (the admin UI, the MCP agent door, the WASM runtime).