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
nullevaluates tonull. - 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 withstr()and concatenated.and/orshort-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 @default must be last.
Data and effect directives
@shape binds a value reshaped with SDA.
@call invokes a declared capability; the page does
not decide network policy itself. The other effect directives —
@session, @cookie, @status, @header, @cache,
@redirect, @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.