Why templates instead of static responses?
A Mock Rule with a static body always returns the same JSON. This works for simple cases, but it can't simulate real backend logic: calculating totals, applying different discounts by plan, returning data from the request itself, or evaluating business rules.
httpdrop's template engine is based on Handlebars.js and solves this without requiring code. You write expressions directly in the Mock Rule's Response Body field — the server evaluates them at runtime and returns the result.
process.env, no prototype pollution (allowProtoPropertiesByDefault: false). Automatic 100ms timeout and output capped at 64KB per response.Syntax in three lines
{{helper arg1 arg2}}
{{helper (subHelper arg)}}
{{#if (operator A B)}}true value{{else}}false value{{/if}}
Arithmetic
Calculate totals, taxes, discounts and conversions in real time. Operators accept literals or subexpressions as arguments.
add{{add 10 5}}15subtract{{subtract 10 3}}7multiply{{multiply 4 2.5}}10divide{{divide 10 4}}2.5modulo{{modulo 10 3}}1floor / ceil / round{{floor 4.9}}4toFixed{{toFixed 9.999 2}}"10.00"{
"qty": "{{body 'qty'}}",
"price": "{{body 'price'}}",
"subtotal": "{{toFixed (multiply (body 'qty') (body 'price')) 2}}",
"tax": "{{toFixed (multiply (multiply (body 'qty') (body 'price')) 0.15) 2}}",
"total": "{{toFixed (multiply (multiply (body 'qty') (body 'price')) 1.15) 2}}"
}
String Manipulation
Transform, normalize and encode strings with chainable operators. Useful for generating slugs, deriving emails from names and URL-safe tokens.
uppercase / lowercase{{uppercase 'hello'}}HELLOconcat{{concat 'foo' 'bar'}}foobarreplace{{replace 'hi world' 'world' 'you'}}hi youtrim{{trim ' hello '}}hellopadStart{{padStart '7' 3 '0'}}007slugify{{slugify 'João Silva'}}joao-silvaurlEncode{{urlEncode 'a b'}}a%20blen / substr{{substr 'hello' 0 3}}hel{
"original": "{{body 'name'}}",
"slug": "{{slugify (body 'name')}}",
"upper": "{{uppercase (body 'name')}}",
"email": "{{concat (lowercase (slugify (body 'name'))) '@example.com'}}",
"initials": "{{uppercase (substr (body 'name') 0 2)}}"
}
Dates and Expiration
The {{now}} helper accepts a format and a relative offset from the request time. Ideal for simulating JWT tokens with iat/exp, creation logs and due dates.
{{now 'iso'}}2026-05-23T14:30:00.000Z{{now 'sql'}}2026-05-23 14:30:00{{now 'unix'}}1748008200000{{now 'br'}}23/05/2026 14:30{{now '{days:30}' 'iso'}}30 days in the future{{now '{days:-7}' 'sql'}}7 days ago (SQL format){{now '{months:3}' 'iso'}}3 months from now{
"tokenId": "{{uuid}}",
"issuedAt": "{{now 'iso'}}",
"expiresAt": "{{now '{days:30}' 'iso'}}",
"expiresIn": 2592000,
"sqlCreated": "{{now 'sql'}}",
"unix": "{{now 'unix'}}"
}
Random Selection
{{oneOf}} returns a different value on each request — perfect for simulating systems with variable states like order status, stock availability or risk analysis results.
{
"orderId": "{{param 'id'}}",
"status": "{{oneOf 'pending' 'processing' 'shipped' 'delivered' 'cancelled'}}",
"carrier": "{{oneOf 'Correios' 'Fedex' 'DHL' 'Jadlog'}}",
"estimatedDays": {{oneOf '1' '2' '3' '5' '7'}}
}
Conditionals Based on Request Data
Use {{#if (operator A B)}} to build responses that depend on any request field. Available comparison operators: eq, gt, lt, gte, lte, isNumber, isInteger, isDate.
{
"score": "{{body 'score'}}",
"approved": "{{#if (gte (body 'score') 600)}}true{{else}}false{{/if}}",
"tier": "{{#if (gte (body 'score') 800)}}platinum{{else}}{{#if (gte (body 'score') 700)}}gold{{else}}standard{{/if}}{{/if}}",
"creditLimit": "{{#if (gte (body 'score') 800)}}50000{{else}}{{#if (gte (body 'score') 700)}}20000{{else}}5000{{/if}}{{/if}}"
}
{
"plan": "{{query 'tier'}}",
"monthlyPrice": "{{#if (eq (query 'tier') 'pro')}}99{{else}}{{#if (eq (query 'tier') 'business')}}299{{else}}0{{/if}}{{/if}}",
"users": "{{#if (eq (query 'tier') 'business')}}unlimited{{else}}5{{/if}}"
}
Request Data
Any data from the incoming request can be used as a variable in the response — JSON body, query params, headers, path params, HTTP method and the full path.
body 'field'{{body 'user.name'}}JSON body field (dot notation supported)query 'param'{{query 'page'}}Query string (?page=2)header 'name'{{header 'x-tenant'}}HTTP header (lowercase)param 'id'{{param 'id'}}Path param (/users/{id})method{{method}}HTTP method of the requestreqPath{{reqPath}}Full request path{
"method": "{{method}}",
"path": "{{reqPath}}",
"queryPage": "{{query 'page'}}",
"contentType": "{{header 'content-type'}}",
"bodyName": "{{body 'name'}}",
"requestId": "{{uuid}}"
}
JWT — Decoding the Request Token
Use {{jwtPayload}} and {{jwtHeader}} to extract fields from the Bearer token sent in the request. Ideal for simulating authenticated endpoints that return data about the user themselves.
{
"userId": "{{jwtPayload (header 'authorization') 'sub'}}",
"email": "{{jwtPayload (header 'authorization') 'email'}}",
"role": "{{jwtPayload (header 'authorization') 'role'}}",
"algorithm": "{{jwtHeader (header 'authorization') 'alg'}}"
}
Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImVtYWlsIjoidGVzdGVAZXhhbXBsZS5jb20iLCJyb2xlIjoiYWRtaW4ifQ.sig as the Authorization header — the engine decodes the payload and returns {"userId":"user_123","email":"teste@example.com","role":"admin","algorithm":"HS256"}. The signature is not validated.Store — Persistent Counters
The K-V Store persists values per key, per endpoint, while the server is running. Ideal for simulating rate limiting, state sequences and call counters.
{
"callCount": {{storeIncr 'hits'}},
"allowed": "{{#if (lte (storeGet 'hits') 3)}}true{{else}}false{{/if}}",
"message": "{{#if (lte (storeGet 'hits') 3)}}OK ({{storeGet 'hits'}}/3){{else}}Rate limit exceeded{{/if}}"
}
Make 5 calls to the endpoint — the first 3 return allowed: "true"; subsequent ones return false. Use {{storeReset 'hits'}} in another rule to reset the counter.
Advanced Faker
The new {{faker 'category.generator' 'options'}} syntax unlocks generators with parameters — numeric ranges, date formats, float precision. Fully compatible with legacy {{faker.field}} syntax.
{{faker 'name'}}{{faker.name}}Ana Ribeiro{{faker 'cpf'}}{{faker.cpf}}123.456.789-09{{faker 'number.int' '{min:300,max:850}'}}—647 (int in range){{faker 'number.float' '{precision:0.01}'}}—3.14{{faker 'date.past' 'iso'}}—2023-07-12T...{{faker 'date.future' 'iso'}}—2027-02-18T...{
"id": "{{faker 'uuid'}}",
"name": "{{faker 'name'}}",
"email": "{{faker 'email'}}",
"cpf": "{{faker 'cpf'}}",
"phone": "{{faker 'phone'}}",
"score": {{faker 'number.int' '{min:300,max:850}'}},
"birthDate": "{{faker 'date.past' 'iso'}}",
"nextReview": "{{faker 'date.future' 'iso'}}",
"address": "{{faker 'address'}}",
"zip": "{{faker 'cep'}}"
}
Backward Compatibility
All legacy syntax is automatically converted to the new syntax before rendering — your existing Mock Rules keep working without any changes.
{{faker.cpf}}{{faker 'cpf'}}{{req.body.field}}{{body 'field'}}{{req.query.x}}{{query 'x'}}{{req.headers.x}}{{header 'x'}}{{req.params.id}}{{param 'id'}}{{env.KEY}}{{env 'KEY'}}{{base64.x,y}}{{base64Combine x y}}{{uuid}} / {{timestamp}}kept as-is (no conversion)Real-world use cases
{{oneOf}} to simulate variable order status and {{faker 'date.future'}} for realistic delivery dates.{{jwtPayload}} and return the user's own data at the /me endpoint — no real database needed.{{storeIncr}} to accumulate counters across calls and simulate real pagination, view counting and per-endpoint rate limiting.{{env 'KEY'}} only accesses Workspace variables — never process.env. {{storeIncr}} resets when the server restarts.