Guides

Prompt templating

Render dates, timezones, provider details, and structured request context in system prompts before the upstream model call.

How rendering works

When prompt templating is enabled on a service model, the gateway resolves supported {{ var_name }} placeholders in system prompts before forwarding them to the upstream model. For example, add request-time date context without changing every client: "Today is {{ now_date }} ({{ now_day_of_week }})."

Templating applies only to:

  • The model-default system prompt configured on the service model (or its template).
  • Any role: "system" message in the request body.

user, assistant, and tool messages are not rendered.

Rendering modes

Each service model (and service-model template) has a template_render_mode setting with three values:

offdefault
Do not render either surface. Placeholder syntax remains literal. This is the default.
varsorg-admin authored
Substitute supported variables in the model default and caller-supplied system messages. Organisation administrators can edit the prompt.
logicmanaged by Servada
Apply Edge template logic to the Servada-managed model default. Caller-supplied system messages still use variable substitution only.

Variable reference

The same variables are available in every rendering mode. Time variables are grouped by how often they change, which affects prompt-prefix caching. See Caching guidance below for why this matters.

Stable tier (recommended default)

These values change at most once per day and are the best default for reusable prompts.

{{ now_year }}integer
Current year as a 4-digit string. Example: 2026.
{{ now_quarter }}string
Q1 to Q4. Example: Q2.
{{ now_month }}string
Zero-padded month (01 to 12). Example: 05.
{{ now_month_name }}string (English)
English month name. Example: May.
{{ now_month_name_local }}string (locale-aware)
Localised month name using inferada.language. Falls back to English when language is unset or unrecognised. Examples: mei (nl), mai (fr).
{{ now_iso_week }}string
ISO 8601 week tag. Example: 2026-W18.
{{ now_date }}string
YYYY-MM-DD UTC date. Example: 2026-05-02.
{{ now_day }}string
Day of month, no padding (1 to 31).
{{ now_day_of_week }}string (English)
English weekday name. Example: Saturday.
{{ now_day_of_week_local }}string (locale-aware)
Localised weekday using inferada.language. Falls back to English. Examples: zaterdag (nl), samedi (fr).

Hourly tier

These values change once per hour. Use them when the prompt needs time-of-day context.

{{ now_hour }}string
UTC hour as 00 to 23. Example: 14.
{{ now_iso_hour }}string
Hour-precision ISO 8601. Example: 2026-05-02T14Z.

Per-request tier (cache-busting)

These values change every minute or second. Use them only when the task needs that precision, because early changes in a system prompt can reduce upstream prefix-cache reuse.

{{ now_time }}string
HH:mm UTC. Example: 14:23 UTC.
{{ now_iso }}string
Second-precision ISO 8601 UTC. Example: 2026-05-02T14:23:11Z.
{{ now_unix }}string
Epoch seconds as a string. Example: 1777989791.

Model & caller facts

{{ model_provider }}string
API format of the selected upstream, such as openai, anthropic, or google.
{{ user_timezone }}string
IANA timezone the wall-clock now_* fields are expressed in. Resolves from inferada.timezone on the request body when present, otherwise from the caller user’s profile setting (Profile → Settings), otherwise UTC. Always set; defaults to "UTC".

The wall-clock now_* fields above (everything except now_iso, now_iso_hour, and now_unix, which stay UTC because they describe an instant) reflect the caller's active timezone. Set it per request with { "inferada": { "timezone": "America/Sao_Paulo" } } on the request body, or per-user under Profile → Settings.

Dot-path and bracket-index access

Nested data from the haystack processor is available through dot paths and numeric bracket indices:

  • Path segments must be snake_case identifiers (lowercase, digits, underscore, starting with a letter). {{ haystack.customer.name }} resolves; raised-case JS-style access does not.
  • Bracket indices accept positive integers only: {{ haystack.orders[0].total }}. Out-of-bounds and non-numeric brackets leave the placeholder literal.
  • Resolution reads plain data properties only. Identifiers such as constructor and __proto__ are rejected, and there is no expression evaluator: you cannot call methods or access globals.

Resolved values stringify automatically: primitives become their String() form, arrays become compact JSON, objects become 2-space-indented JSON. null and missing values render as empty string and an unmatched template literal respectively.

Choose the least precise time value you need

Providers may cache a matching prompt prefix. Keeping the beginning of a system prompt stable can improve cache reuse.

A per-request value changes near every render. Placing one near the beginning of a long system prompt can prevent reuse of the content that follows it.

Prefer stable variables for date context, hourly variables for time-of-day behaviour, and per-request variables only for tasks that require minute or second precision.

Worked example

The same intent, three ways:

"Today is {{ now_date }}."cache rolls over once per day
Recommended. The rendered prefix stays stable through the current calendar day.
"It is {{ now_iso_hour }}."cache rolls over once per hour
Use when the response depends on the current hour. The rendered prefix remains stable within that hour.
"The current time is {{ now_iso }}."cache rolls over every request
Use for relative-time calculations that require the current instant. Prefer the date for general temporal context.

See provider-specific docs for the exact caching mechanics: Anthropic prompt caching and OpenAI prompt caching.

Writing effective templates

The engine your prompt is rendered through depends on the mode and the surface:

vars mode (both surfaces)logic-less
Supports {{ var_name }} substitution and safe dot or bracket paths. Use \{{ for a literal opening delimiter. Method calls, globals, and conditional logic are not available.
logic mode (model-default only)Edge (full)
Full Edge templating: @if / @elseif / @else / @endif, @unless, @each, @let, @assign. Not available: @include, @layout, @component, @inject, @svg, @vite; these will fail to render. Servada manages the authoring; reach out to Servada support to update the prompt.
logic mode (your role:"system" message)logic-less
Same engine as vars mode. The gateway never applies Edge to caller-supplied content, regardless of mode. So if you need conditional logic in your own prompt, request a logic-mode template instead and let it own that branch.

Prefer simple templates

Plain {{ var }} substitutions are easier to review and have predictable failure behaviour. Add @if or @each only when the model default needs conditional content.

Render failure mode

Escaping literal {{

If you genuinely need the characters {{ to appear in the rendered output (for example, in instructions to the model about how to format a future template), escape them:

  • In vars mode: write \{{ and the gateway emits a literal {{.
  • In logic mode (Edge): use Edge's convention @{{ … }} for a literal.

Worked patterns

Add the current date

text
You are a helpful assistant. Today is {{ now_date }} ({{ now_day_of_week }}).
When the user asks about events or deadlines, anchor your reasoning to that date.

The rendered value changes once per day, so it is suitable for a reusable prompt prefix.

Provider-aware tone

text
You are running on {{ model_provider }}. Format mathematical expressions in
plain text rather than markdown, since rendering varies by client.

Localised greeting

text
Begin your response with a friendly greeting in the user's language.
Today is {{ now_day_of_week_local }} {{ now_day }} {{ now_month_name_local }}.

Pair the _local variants with inferada.language on the request: pass language: "nl" and you'll get zaterdag 2 mei instead of Saturday 2 May.

Add time-of-day context

text
The current hour is {{ now_hour }} UTC. If you reference work hours,
assume European business time (08–18 UTC).

This is the hourly tier: it rolls over once per hour, so the cache stays effective within the hour.

What a Servada-authored logic template looks like

An example of what logic mode can do (Servada authors this for you on request; you can't put logic into your own request body):

text
Today is {{ now_date }}.

@if(model_provider === 'anthropic')
You are running on Anthropic Claude. Lean on extended thinking when the user asks for
multi-step reasoning.
@else
You are running on {{ model_provider }}. Keep responses concise.
@endif

Switch out of Advanced mode

Servada manages a logic-mode prompt while the model is in Advanced mode. An organisation administrator can stop using it in either of these ways:

  • Permanent: an org admin switches template_render_mode to vars or off. The prompt content unlocks for editing immediately. The cost is Edge logic: you keep variable substitution in vars mode and lose it entirely in off.
  • Per-request: send your own role: "system" message with inferada.system_mode: "replace_default" to skip the managed default for that call. Your own system prompt still gets variable substitution via the logic-less engine, so {{ now_date }} etc. work in your custom prompt with no mode change.