Request, forms & sessions

Every page gets the same typed inputs. Read them; don't reshape them by hand.

Three bindings, always present

Every route executes with three reserved top-level bindings that cannot be reassigned:

  • request — the current HTTP request: method, normalized path, decoded route params, query, headers, cookies, and the parsed body when the route policy declares one.
  • context — host- and deployment-provided facts: the resolved authority, environment values declared as public, session identity, and capability status.
  • data — the entry point for declared data capability operations.

Missing keys are null

Reading a key that is not present yields null, and null propagates through further lookups. A template that reads request.body.email on a GET route renders an empty string rather than failing. Use exists(...) and empty(...) to branch explicitly:

@if (empty(request.query.q)) {
  <p>Type a search term.</p>
} @else {
  <p>Results for {{= request.query.q }}</p>
}

Conventions that keep templates predictable

  • Read, don't reshape. Keep display logic in the page thin. When data arrives in the wrong shape, use SDA via &#64;shape rather than nested loops and conditionals.
  • One canonical name per fact. Don't copy request.user into a local and read both; refer to the canonical path.
  • Error context is public-only. Error handlers never receive secret values, so don't design pages that depend on secrets being present during a 500.
  • Body only where declared. request.body is populated only when the route policy sets body = "json" / "form" / "raw" and the method is allowed.

Sessions

Sessions are volatile and server-held — they are never a durable store. Set and clear session keys with the &#64;session effect; the framework issues and rotates the cookie. In production the session lives in replicated host memory, not on the page node's disk, so nodes stay replaceable.

@call id = auth.callback()
@session user = id.value.subject
@redirect "/dashboard/", status: 303

Forms

Declare a mutating route's methods and body type in the manifest, then read request.body:

[routes."/subscribe"]
methods = ["POST"]
body = "form"

Cross-origin state-changing requests without a verified token are rejected before the page runs.