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.
Inferada never evaluates @if or @each in system messages sent by the caller. Ask Servada to configure a logic-mode model default when conditional rendering is required. To bypass that default for one request, send your own system message with inferada.system_mode: "replace_default".
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
constructorand__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
If parsing or rendering fails, the gateway forwards the literal source template to the upstream model. Raw @if or {{ variable }} syntax may therefore appear in its system prompt. Test a logic template before enabling it for production traffic.
The gateway logs every render failure with its reason (syntax error, undefined variable, timeout, oversize), but never the prompt text itself. If a render misbehaves, contact Servada support with your request_id and they can identify the failure.
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
varsmode: write\{{and the gateway emits a literal{{. - In
logicmode (Edge): use Edge's convention@{{ … }}for a literal.
Worked patterns
Add the current date
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
You are running on {{ model_provider }}. Format mathematical expressions in
plain text rather than markdown, since rendering varies by client.Localised greeting
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
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):
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.
@endifSwitch 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_modetovarsoroff. The prompt content unlocks for editing immediately. The cost is Edge logic: you keep variable substitution invarsmode and lose it entirely inoff. - Per-request: send your own
role: "system"message withinferada.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.