Templating

A small syntax for server-rendered pages. Missing data is null, not a crash.

A page is literal markup with interpolation and a small set of @ directives. It is executed on the server for each request and produces the response body. There is no client runtime unless you add one yourself.

The value model

Values are JSON-shaped: null, booleans, finite numbers, strings, arrays and objects. NaN, infinities, functions, host handles and cyclic structures can never become page-visible values. Object iteration order is lexicographic by key; array order is insertion order.

The three reserved top-level bindings — request, context and data — cannot be reassigned.

Lookup and missing data

Paths walk objects and arrays: user.profile.name, items[0], settings["theme"].

  • A missing object key evaluates to null.
  • Indexing null evaluates to null.
  • An out-of-range array index evaluates to null.
  • A negative or non-integer index, or indexing a scalar, is a runtime error.

Templates therefore degrade to empty output on missing data instead of crashing.

Truthiness

null, false, numeric zero, the empty string, the empty array and the empty object are false. Everything else is true. bool(value) returns truthiness; truthiness never implicitly coerces for arithmetic or ordering.

Operators

Precedence, highest first: calls and indexing; unary not / ! / -; * /; + -; comparisons; = !=; and; or.

  • -, *, / require numbers; division by zero is a runtime error.
  • + adds two numbers; if either side is a string, both are converted with str() and concatenated.
  • and / or short-circuit and return booleans.
  • = / != use canonical structural equality; < <= > >= compare two numbers or two strings.

Output

{{ expression }}    HTML-escaped scalar text
{{= expression }}   canonical form, identical to the above
{{~ expression }}   raw text (strings and byte-safe helpers only)

Escaping maps & < > " ' to entities. null emits an empty string. Arrays and objects require an explicit json(...) or raise language.non_scalar_output.

Pipelines read left to right:

{{= user.name | trim | upper }}

Built-ins

upper lower trim len contains starts_with ends_with json str exists empty num bool merge sda. Every built-in validates arity and types; type errors surface as language.type_error.

Includes

@include("_partials/_header.html.crisp")

Includes are resolved entirely at build time. Paths must be static string literals, cannot escape the application root, and cannot form cycles. Missing includes are compile errors. There is no runtime filesystem include in a production Capsule.

Control directives

@if (user.admin) {
  <span>Admin</span>
} @else {
  <span>Member</span>
}

@for (item of cart.items) {
  <li>{{= item.title }}</li>
}

@for ((key, value) of settings) {
  <dt>{{= key }}</dt><dd>{{= value }}</dd>
}

@switch (user.role) {
  @case ("admin") { ... }
  @default { ... }
}

Array loops preserve order; object loops use deterministic key order. The loop variable is lexical to the body. Switch uses canonical equality, runs at most one case, and &#64;default must be last.

Data and effect directives

&#64;shape binds a value reshaped with SDA. &#64;call invokes a declared capability; the page does not decide network policy itself. The other effect directives — &#64;session, &#64;cookie, &#64;status, &#64;header, &#64;cache, &#64;redirect, &#64;telemetry — emit typed effect records that the host validates and commits after the page finishes.

Execution limits

Every build declares limits: render time, instruction budget, response bytes, capability operations and concurrency. Error pages run with one quarter of the normal render budget and cannot perform mutating capability operations.