Skip to content

Templates in answers

Server answers can contain placeholders in double braces. When a server answers, it replaces them with values from the request being answered, the message being replied to, your variables, or generated values such as a fresh UUID:

A mock route's body
{
"id": "{{request.params.id}}",
"search": "{{request.query.q}}",
"requestedBy": "{{request.headers.x-user}}",
"traceId": "{{$uuid}}",
"region": "{{region}}"
}

This page lists everything a template can contain. Templates are plain text substitution: there are no conditions, loops, filters or helper functions.

WhereRequest values {{request.…}}{{message}}Variables and dynamic values
Mock API: route bodyYesNoYes
Mock API: route header names and valuesYesNoYes
Mock API: route status, method, pathNot templated
Mock API: Match only when valuesNoNoYes
Mock API: Backend URL (forwarding)NoNoYes
Event stream server: event data and idYes, from the request that opened the streamNoYes
Event stream server: event nameNot templated
Event stream server: data of events sent from the traffic panelYes, from each client’s requestNoYes
WebSocket server: greetingNoNoYes
WebSocket server: rule repliesNoYesYes
TCP server: greeting (text encoding)NoYes, always emptyYes
TCP and UDP servers: rule replies (text encoding)NoYesYes
TCP and UDP servers: anything in hex encodingNot templated
Reply rule patternsNot templated
DNS records, relay targetNot templated

Text you type in a server’s traffic panel and send to WebSocket, TCP, UDP or relay clients is different: the app fills in your variables before it sends, like it does for requests.

  • A placeholder is a name between {{ and }}. Spaces around the name are allowed: {{ request.params.id }} works like {{request.params.id}}. The one exception is {{message}}, which must be written exactly like that.
  • A placeholder can’t span lines. {{}} and a {{ without a closing }} stay as they are.
  • A variable that isn’t defined stays as written: {{token}} is sent as the text {{token}}. (So does a secret variable, see below.)
  • A request value that isn’t there (a missing header, an unknown key) becomes an empty string.
  • There is no escape syntax. Text that looks like a placeholder but names nothing defined is sent unchanged.
  • When {{ appears inside another pair of braces, only the innermost complete placeholder counts: {{ a {{host}} }} becomes {{ a example.test }}.

Available in mock routes and event stream servers. For an event stream, “the request” is the GET that opened the stream.

PlaceholderValueExample: POST /users/42?q=a%20b&tag=x
{{request.method}}The methodPOST
{{request.path}}The path as sent, without the query (not decoded)/users/42
{{request.url}}Path and query as sent (not decoded)/users/42?q=a%20b&tag=x
{{request.body}}The request body as text (bytes that aren’t UTF-8 become �)the body
{{request.params.NAME}}The path parameter :NAME or {NAME} of the route, decoded{{request.params.id}} → 42
{{request.params.*}}What a trailing * in the route path matched, decoded/files/* on /files/a/b.txt → a/b.txt
{{request.query.NAME}}The query parameter, decoded (%XX and +). The first one when it repeats. The name is case-sensitive.{{request.query.q}} → a b
{{request.headers.NAME}}The request header, name in any case. The first one when it repeats.{{request.headers.content-type}}

In event stream servers the body is always empty and there are no path parameters.

There is no way to reach into the body: {{request.body.user.name}} is an unknown key and becomes empty. To echo a field, echo the whole body, or add a route per case with Body contains conditions.

Request values are never expanded again: a client that sends {{token}} in its body and gets it back through {{request.body}} receives the text {{token}}, never the value of your token variable.

In WebSocket, TCP and UDP reply rules, {{message}} is the message being answered, as text:

  • Trailing line breaks (\r, \n) are removed, so line protocols echo cleanly.
  • Bytes that aren’t UTF-8 become �.
  • Like request values, the message is never expanded: a client sending {{token}} gets {{token}} back.
A rule: Matches regex ^GET → reply
value of {{message}} at {{$isoTimestamp}}

A TCP greeting is rendered like a reply to an empty message, so {{message}} is empty there. In a WebSocket greeting, {{message}} is not special: it stays as written unless you have a variable named message.

Every other name is a variable. Servers use:

  1. the active environment’s variables (including values that scripts set on this computer), then
  2. the workspace variables (their saved values).

The first one that defines a name wins. Global variables (the ones scripts set) are not used.

A variable’s value may itself contain placeholders for other variables or dynamic values (up to 10 levels deep), but not request values or {{message}}.

A running server reads the variables when it starts, and again whenever its settings change while it runs. After you switch the environment or change a variable, restart the server (or make any edit to it) to use the new values. With zorvik serve, --env chooses the environment and --var key=value sets variables.

Names starting with $ generate a value each time they appear:

PlaceholderValueExample
{{$uuid}}A random UUID (version 4)3b241101-e2bb-4255-8caf-4136c566a962
{{$guid}}, {{$randomUUID}}Same as {{$uuid}}
{{$timestamp}}Unix time in seconds1790000000
{{$timestampMs}}Unix time in milliseconds1790000000123
{{$isoTimestamp}}The current time in UTC, RFC 3339 with fractional seconds2026-09-28T09:15:42.482913Z
{{$randomInt}}A whole number from 0 to 1000417
{{$randomBoolean}}true or falsetrue
{{$randomAlphaNumeric}}One character, a–z or 0–9k
{{$randomEmail}}user + four digits + @example.comuser4821@example.com

Two {{$uuid}} in one answer are two different UUIDs. A variable you define with the same name (for example $timestamp) is used instead of the generated value. Other $ names are not defined and stay as written.

A rendered mock response, event or WebSocket greeting can grow to 32 MB; placeholders past that point are left as written. One variable placeholder can expand to at most 16 MB, and variables nested more than 10 levels deep (for example two that refer to each other) are left partly unexpanded.

Echo the request (a mock route, ANY /echo):

Response body
{{request.method}} {{request.url}}
Content-Type: {{request.headers.content-type}}
{{request.body}}

Pass a header through (a response header row):

HeaderValue
X-Request-Id{{request.headers.x-request-id}}
Location/users/{{request.params.id}}

Personalized event stream (an event stream server, opened with GET /events?user=ann):

EventDataId
hello{"user": "{{request.query.user}}", "env": "{{envName}}"}1
tick{"at": "{{$isoTimestamp}}"}

Reply rules (a TCP server with line framing):

WhenMessageReply
Is exactlyPINGPONG
Matches regex^GET value of {{message}}
Containsid{{$uuid}}