Apicentric uses Handlebars as its templating engine to generate dynamic API responses. You can access request data, fixtures, and runtime state to create realistic, context-aware responses.
Template basics
Templates use double curly braces {{}} to insert dynamic values:
endpoints :
- method : GET
path : /users/{id}
responses :
200 :
content_type : application/json
body : |
{
"id": "{{params.id}}",
"name": "{{fixtures.user.name}}",
"timestamp": "{{now}}"
}
Context variables
Templates have access to several context objects:
Request context
{{ request.method }} # HTTP method (GET, POST, etc.)
{{ request.path }} # Request path
{{ request.query.key }} # Query parameter
{{ request.headers.authorization }} # Request header
{{ request.body.field }} # Request body field
Path parameters
# For path /users/{id}
{{ params.id }}
Fixtures
# Access fixture data defined in your YAML
{{ fixtures.users }}
{{ fixtures.products.0.name }}
Runtime data
# Dynamic data set during request processing
{{ runtime.sessionId }}
{{ runtime.requestCount }}
Environment variables
{{ env.API_KEY }}
{{ env.BASE_URL }}
Built-in helpers
Apicentric provides a comprehensive set of Handlebars helpers organized into categories.
Faker helpers
Generate realistic sample data:
Current timestamp
Random UUID
Random number
Random string
Fake email
Fake name
Lorem ipsum
{{ now }}
# Output: "2026-03-01T12:34:56.789Z"
Text helpers
String manipulation:
Uppercase
Lowercase
Contains check
Starts with
Ends with
Regex match
{{ upper "hello" }}
# Output: "HELLO"
Math and logic helpers
Array length
Logical NOT
Logical AND
Logical OR
Equality
Not equal
Greater than
Less than or equal
Exists check
{{ length fixtures.items }}
# Output: Number of items in array
Core helpers
Data manipulation:
Find in array
Find by field
Find by multiple fields
Filter array
JSON serialization
Merge objects
Select fields
Default value
{{ find fixtures.users "id" 123 }}
# Returns user object where id equals 123
Bucket helpers
Store and retrieve data across requests:
{{ bucket_set "counter" 42 }}
# Stores value in shared data bucket
Bucket helpers allow you to maintain state across multiple requests. Use them for counters, session data, or temporary storage.
Conditional rendering
Use Handlebars built-in if helper:
{{ #if ( eq request.method "POST" ) }}
{
"message": "Resource created",
"status": "success"
}
{{ else }}
{
"message": "Resource retrieved"
}
{{ /if }}
Loops
Iterate over arrays:
{
"users": [
{{ #each fixtures.users }}
{
"id": " {{ this.id }} ",
"name": " {{ this.name }} "
} {{ #unless @last }} , {{ /unless }}
{{ /each }}
]
}
Complex example
Combine multiple helpers for realistic responses:
fixtures :
products :
- id : 1
name : "Laptop"
price : 999
stock : 50
- id : 2
name : "Mouse"
price : 29
stock : 200
endpoints :
- method : GET
path : /products
responses :
200 :
content_type : application/json
body : |
{
"timestamp": "{{now}}",
"method": "{{request.method}}",
"total": {{length fixtures.products}},
{{#if request.query.category}}
"filter": "{{request.query.category}}",
{{/if}}
"products": [
{{#each fixtures.products}}
{
"id": {{this.id}},
"name": "{{this.name}}",
"price": {{this.price}},
"stock": {{this.stock}},
"available": {{gt this.stock 0}},
"sku": "{{upper this.name}}-{{random_string 6}}"
}{{#unless @last}},{{/unless}}
{{/each}}
]
}
Creating custom helpers
You can extend the template engine with custom helpers by modifying the source code:
Add a new file in src/simulator/template/helpers/:
use handlebars :: { Context , Handlebars , Helper , HelperResult , Output , RenderContext };
pub fn register ( handlebars : & mut Handlebars ) {
handlebars . register_helper ( "reverse" , Box :: new ( reverse_helper ));
}
fn reverse_helper (
h : & Helper ,
_ : & Handlebars ,
_ : & Context ,
_ : & mut RenderContext ,
out : & mut dyn Output ,
) -> HelperResult {
if let Some ( param ) = h . param ( 0 ) {
if let Some ( s ) = param . value () . as_str () {
let reversed : String = s . chars () . rev () . collect ();
out . write ( & reversed ) ? ;
}
}
Ok (())
}
Add to src/simulator/template/helpers/mod.rs:
Then register in src/simulator/template/mod.rs:
helpers :: custom :: register ( handlebars );
{{ reverse "hello" }}
# Output: "olleh"
Template preprocessor
Apicentric includes a template preprocessor that supports pipe syntax (though the standard Handlebars syntax is recommended).
Debugging templates
When a template fails to render, you’ll see a detailed error:
Template rendering failed: Variable `fixtures.invalid` not found
Check template syntax and available context variables
Use the json helper to inspect complex objects: {{json fixtures.user}}
Pre-compiled templates
For better performance, compile templates once and reuse:
let mut engine = TemplateEngine :: new () ? ;
// Compile template
engine . compile_template ( "user_response" , template_string ) ? ;
// Render compiled template
let result = engine . render_compiled ( "user_response" , & context ) ? ;
Template caching
The template engine automatically handles caching internally when using render_template().
Best practices
Keep templates simple - Complex logic belongs in scripts or code
Use helpers - Leverage built-in helpers instead of complex expressions
Validate JSON - Ensure generated JSON is valid with proper escaping
Test thoroughly - Test templates with various context values
Use fixtures - Define test data in fixtures rather than hardcoding
Document context - Comment which context variables are expected
Common patterns
Paginated responses
{
"page": {{ default request.query.page 1 }} ,
"limit": {{ default request.query.limit 10 }} ,
"total": {{ length fixtures.items }} ,
"items": {{ filter fixtures.items "active" true }}
}
Error responses
{{ #if ( not ( exists params.id )) }}
{
"error": "validation_error",
"message": "ID parameter is required",
"timestamp": " {{ now }} "
}
{{ else }}
{
"id": " {{ params.id }} ",
"data": {{ find fixtures.items "id" params.id }}
}
{{ /if }}
Templates can also be used in response headers:
responses :
200 :
headers :
X-Request-ID : "{{random 'uuid'}}"
X-Timestamp : "{{now}}"
body : "..."
Further reading
Be careful with user input in templates. Always validate and sanitize data to prevent injection attacks.