Skip to main content
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:
{{now}}
# Output: "2026-03-01T12:34:56.789Z"

Text helpers

String manipulation:
{{upper "hello"}}
# Output: "HELLO"

Math and logic helpers

{{length fixtures.items}}
# Output: Number of items in array

Core helpers

Data manipulation:
{{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:
1
Create a helper module
2
Add a new file in src/simulator/template/helpers/:
3
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(())
}
4
Register the helper
5
Add to src/simulator/template/helpers/mod.rs:
6
pub mod custom;
7
Then register in src/simulator/template/mod.rs:
8
helpers::custom::register(handlebars);
9
Use your helper
10
{{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}}

Performance optimization

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}}

Dynamic headers

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.

Build docs developers (and LLMs) love