Response Templating
Mock responses don’t have to be fully static JSON. Use {{ }} double-brace syntax inside any string value in response (or a sequence step’s response) to echo back data from the incoming request — no custom code required.
This dramatically cuts down on how many mock files you need. Instead of one file per id, username, or header value, one templated mock covers all of them.
Template sources
Section titled “Template sources”| Template | Source |
|---|---|
{{path.id}} |
A named path parameter :id or {id} |
{{query.page}} |
URL query parameter ?page=2 |
{{header.x-request-id}} |
Request header value (case-insensitive) |
{{body.username}} |
Top-level JSON (or form) body field |
{{body.user.email}} |
Nested JSON body field, using dot notation |
{{faker.uuid}} |
Randomly generated data — no request input needed |
Example
Section titled “Example”{ "method": "POST", "path": "/users", "status": 201, "response": { "id": 99, "username": "{{body.username}}", "email": "{{body.email}}", "created_by": "{{header.x-actor}}", "self_url": "/users/99" }}curl -X POST http://localhost:8080/users \ -H "X-Actor: admin" \ -H "Content-Type: application/json" \{ "id": 99, "username": "alice", "created_by": "admin", "self_url": "/users/99"}Combined with path parameters
Section titled “Combined with path parameters”Templating is what makes path parameters useful for more than routing — the captured value can flow straight into the response:
{ "method": "GET", "path": "/users/:id", "status": 200, "response": { "id": "{{path.id}}", "name": "Mock User" }}curl http://localhost:8080/users/42# { "id": "42", "name": "Mock User" }Array indexing
Section titled “Array indexing”{{body.…}} templates can index into arrays as well as objects — a zero-based integer segment reads an array element, in either dot or bracket syntax:
{ "method": "POST", "path": "/orders", "status": 201, "response": { "first_sku": "{{body.items.0.sku}}", "first_sku_bracket": "{{body.items[0].sku}}", "first_qty": "{{number:body.items[0].qty}}" }}curl -X POST http://localhost:8080/orders \ -H "Content-Type: application/json" \ -d '{"items":[{"sku":"WIDGET-1","qty":3},{"sku":"WIDGET-2","qty":1}]}'{ "first_sku": "WIDGET-1", "first_sku_bracket": "WIDGET-1", "first_qty": 3}- Dot and bracket syntax are interchangeable, at every level.
body.items.0.sku,body.items[0].sku, and evenbody.a.0.b.1(nested arrays, mixed with object fields) all resolve the same way. - Object keys win over array indices. A JSON object whose keys happen to look numeric (
{"0": "zero"}) still resolves by key lookup first; only an actual array is indexed by position. - Out-of-range and non-numeric indices degrade gracefully — an empty string (or
nullunder ajson:cast), never a panic — the same as any other missing key. - Every typed cast below works on an indexed path:
{{number:body.items.0.qty}},{{json:body.items.0}}, and so on.
Faker generators
Section titled “Faker generators”The faker source doesn’t read the request at all — it generates a fresh, plausible-looking value on every call, so one mock file can stand in for a whole fixture set.
| Template | Output |
|---|---|
{{faker.uuid}} |
RFC 4122 v4 UUID, e.g. 3fa85f64-5717-4562-b3fc-2c963f66afa6 |
{{faker.int}} |
Random integer in the default range 0..=1000000 |
{{faker.int min=1 max=100}} |
Random integer in [1, 100] |
{{faker.bool}} |
true or false |
{{faker.name}} |
Random name, e.g. Priya Novak |
{{faker.email}} |
Slugified random name at example.com, e.g. [email protected] |
{{faker.timestamp}} |
Current UTC time in RFC 3339, e.g. 2026-07-22T16:12:54.481+00:00 |
{ "method": "GET", "path": "/faker/user", "status": 200, "response": { "id": "{{faker.uuid}}", "name": "{{faker.name}}", "email": "{{faker.email}}", "age": "{{faker.int min=18 max=99}}", "verified": "{{faker.bool}}", "created_at": "{{faker.timestamp}}" }}curl http://localhost:8080/faker/user returns a different value on every call:
{ "id": "9c0f1b6d-2f4e-4a1e-b0a2-6c1d7f3e88a4", "name": "Priya Novak", "age": "37", "verified": "true", "created_at": "2026-07-22T16:12:54.481+00:00"}- Every occurrence resolves independently — two
{{faker.uuid}}in one response produce two different UUIDs, and{{faker.email}}is not derived from a{{faker.name}}sitting next to it. - Faker values are always rendered as JSON strings, since templates are interpolated into string values — unless you add a typed cast.
- Malformed arguments degrade to the generator’s defaults instead of failing:
{{faker.int min=abc}}and{{faker.int min=100 max=1}}both use the default0..=1000000range. - An unknown generator (e.g.
{{faker.credit_card}}) renders as an empty string, like any other unknown template. - Resolved after matching, same as every other source — a
{{faker.*}}expression sitting in a matcher is treated as a literal string, not evaluated.
Typed casts (opt-in)
Section titled “Typed casts (opt-in)”By default every templated value is spliced into a JSON string, even when the source is a number or boolean — {"id": "{{path.id}}"} on GET /users/42 renders {"id": "42"}, not {"id": 42}. That’s a hard failure for a typed client (zod, Go’s encoding/json, Rust’s serde_json, Jackson, …), which rejects a string where a number or object belongs. Prefix any source expression with number:, bool:, or json: to render it as that JSON type instead:
| Template | Renders as |
|---|---|
{{number:path.id}} |
JSON number |
{{bool:query.active}} |
JSON boolean (true/false) |
{{json:body.user}} |
JSON value parsed from the source (object, array, number, …) |
{ "id": "{{number:path.id}}", "views": "{{number:faker.int min=1 max=999}}", "active": "{{bool:query.active}}", "user": "{{json:body.user}}"}renders {"id": 42, "views": 317, "active": true, "user": {"name": "alice"}} instead of every field coming back quoted.
- Whole-string only. A cast applies only when the string is exactly one
{{cast:source.key}}expression."user-{{number:path.id}}"stays plain string interpolation — a number can’t be spliced into the middle of a string and still be a number — and renders"user-42". - Failed casts degrade, never panic.
{{number:path.slug}}on/users/abcfalls back to the plain string"abc", and{{json:query.x}}on a value that isn’t valid JSON falls back the same way. - Null handling differs by cast.
{{json:body.missing}}on a field that wasn’t sent renders JSONnull;{{number:...}}and{{bool:...}}fall back to an empty string on a missing key, same as an unprefixed template. - An unrecognized cast word (anything other than
number,bool, orjson) resolves to an empty string, the same as an unrecognized source. - Works everywhere templates work —
response, sequence step responses, and aresponse_filewithtemplate: true— and with every source (path,query,header,body,faker).
Semantics
Section titled “Semantics”- Templates are resolved after the mock (or sequence step) is chosen, so the interpolated value never affects matching itself — you can’t use a template to influence which mock wins.
- An unknown source, a missing key, or an explicit JSON
nullall resolve to an empty string. Malformed or unresolvable templates never panic and never leak the raw{{ }}text into the response. - Non-string body values (numbers, booleans, nested objects/arrays) render using their JSON text form —
{{body.age}}for{"age": 30}produces30(unquoted), not"30". - A response with no
{{ }}expressions is returned unchanged — there’s no templating overhead if you don’t use it. - Header lookups are case-insensitive:
{{header.X-Request-Id}}and{{header.x-request-id}}are equivalent.
Common gotchas
Section titled “Common gotchas”{{path.id}}requires the mock’spathto actually capture that parameter. If your path is/users/:idbut you reference{{path.slug}}, it silently renders as an empty string — there’s no error, so double-check the parameter name matches.- Query values are always strings, same as with query parameter matching.
{{query.page}}for?page=2renders"2", not2. - Templates only work inside
responsestring values. You can’t template intostatus,method,path, or matcher fields.