The template context
When you generate code, Restorm passes your entire design to the template
as a ctx object. Your models, your enums, your routes and above all
their intentions (searchable, PII, cache, and the model’s whole
operational profile: volume, access pattern, traffic, freshness, sensitivity,
retention) are there — a template reads them to decide what to generate. No
variable needed for that: the information you’ve already entered in the design
is directly available.

The shape of ctx
Section titled “The shape of ctx”ctx├─ design # name, version, description, basePath, defaultAuth, vars├─ enums[] # name, description, values[]├─ models[] # name, description, inherits, identifier, timestamps, softDelete,│ # volumetry, accessPattern, traffic, freshness, sensitivity,│ # retention, properties[], allProperties[], examples[], vars└─ groups[] # name, basePath, versionPrefix, headers[], routes[] └─ routes[] # method, path, fullPath, params[], body, responses[], # auth, pagination, cacheSeconds, idempotent, deprecated, varsReferences are by name (never by internal identifier):
property.enum is the enum’s name, property.references.model the name of the
target model, model.inherits the parent’s name. On a model, properties[]
lists its own properties (for the type declaration) and
allProperties[] the own + inherited, flattened properties (for a
complete instance: example data, SQL columns, request body).
Reading a flag: the golden rule
Section titled “Reading a flag: the golden rule”Boolean flags are present in ctx only when they are enabled.
The engine renders in StrictUndefined: so you must test for the presence
of the key, never its value.
{# ✅ correct — testing for presence #}{% if 'searchable' in p %}INDEX({{ p['name'] }}){% endif %}
{# ❌ wrong — raises an error when the flag is absent #}{% if p.searchable %}…{% endif %}Only property.required (boolean) and route.auth (enumeration) are
always present. The other boolean flags are “present = true”; the
value fields (volume, operational profile, cache…) carry their
value when they’re filled in, and are absent otherwise — same presence
rule.
| On a property | On a model | On a route |
|---|---|---|
readOnly · writeOnly · nullable | timestamps · softDelete (booleans) | idempotent · deprecated (booleans) |
unique · searchable · immutable · pii | Operational profile (values): volumetry · accessPattern · traffic · freshness · sensitivity · retention | cacheSeconds (number) · pagination (object) |
A model’s operational profile values: accessPattern
(readHeavy / writeHeavy / balanced / appendOnly), traffic
(low / medium / high), freshness (strong / shortCache / longCache),
sensitivity (public / internal / confidential / pii), retention
(permanent / archivable / ephemeral), volumetry (hundreds /
tenThousands / millions).
Example: caching read-heavy data
Section titled “Example: caching read-heavy data”Two cases, depending on whether the information already exists in the design or not.
a) The design already carries the info
Section titled “a) The design already carries the info”If you’ve set a cache duration on a route, it arrives in
route['cacheSeconds']; route['idempotent'] tells you it’s safe to
cache (read-only):
{% for group in ctx['groups'] %}{% for route in group['routes'] %}{% if 'cacheSeconds' in route and 'idempotent' in route %}// {{ route['method'] }} {{ route['fullPath'] }}app.use("{{ route['fullPath'] }}", cache({{ route['cacheSeconds'] }}));{% endif %}{% endfor %}{% endfor %}b) “Read-heavy” is a native field of the model
Section titled “b) “Read-heavy” is a native field of the model”No variable needed: “read-heavy” is directly the model’s access
pattern. In the model’s settings, Operational profile group,
you choose Read-heavy — the template reads it in
model['accessPattern']. Combine it with model['freshness'] (freshness
tolerance) to decide whether to cache, and for how long:
{% for model in ctx['models'] %}{% if model['accessPattern'] == 'readHeavy' and model['freshness'] != 'strong' %}{% set ttl = 3600 if model['freshness'] == 'longCache' else 60 %}registerCache("{{ model['name'] }}", {{ ttl }}); // cache enabled ({{ ttl }}s){% endif %}{% endfor %}Note the != 'strong' test: a read-heavy model but with strong
consistency must not be cached. That’s the whole point of having
both intentions side by side in the design.
Reserve generation variables for what the design doesn’t already carry (a template-specific setting): the business intentions — volume, access pattern, traffic, freshness, sensitivity, retention — are native fields of the model.
The full contract
Section titled “The full contract”This context is versioned (ctx['contextVersion']) and described exhaustively by
a JSON-Schema shipped in the application repository
(docs/contributing/design-codegen-context.schema.json) — copy it into your
template so its CI validates the reference context. An AI agent gets the
same contract on the fly via the MCP tool get_design_template_contract.