Use dynamic variables and templates to create personalized, data-driven conversations.
UniversalChatbot uses Handlebars templating syntax to insert dynamic values into your flow messages, API requests, conditions, and other configurations. This allows you to personalize conversations and work with data collected during the flow.
With templating, you can:
Handlebars expressions are wrapped in double curly braces: {{expression}}
To output the value of a variable, use its name within double curly braces:
Hello, {{name}}! Welcome to our service.
If name is set to "Sarah", this renders as: Hello, Sarah! Welcome to our service.
Access nested properties using dot notation:
Your order total is {{order.total}} with {{order.items.length}} items.
Variables in your templates come from several sources:
Variables created by nodes during flow execution. For example, a Prompt node that stores user input in a variable called user_email:
We'll send confirmation to {{user_email}}
Data returned from API Call nodes is stored in the variable you specify. Access the response body using dot notation:
{{api_response.body.user.name}}
{{api_response.body.account.balance}}
{{api_response.status_code}}
When you reference an array or object directly, it's rendered as JSON:
Your tags: {{user.tags}}
Renders as: Your tags: ["vip","enterprise"]
Array Index Access Not Supported
You cannot access specific array elements by index. Patterns like items.0, items[0], or data.results.0.name will not work. Use #each to iterate over arrays instead.
Variables available in your templates come from different sources:
| Source | Variables | Description |
|---|---|---|
| Flow Data | {{variable_name}} | Variables set by nodes (Prompt, API Call, etc.) |
| Date & Time | {{@today}}, {{@now}} | Current date (YYYY-MM-DD) and timestamp |
| Request Context | {{@request.platform}}, {{@request.user.*}} | Platform identifier and user information |
| Platform Data | {{@intercom.*}} | Platform-specific data (e.g., Intercom contact info) |
These variables are always available in every flow:
{{@today}}{{@now}}{{@request.platform}}{{@request.user.id}}{{@request.user.name}}{{@request.user.email}}{{@request.user.phone}}When deployed to specific platforms, additional variables become available with user and context data from that platform. See the Deployments documentation for platform-specific variable references.
What Works
{{name}}, {{user.email}}{{response.body.data.id}}{{@today}}, {{@user_id}}{{#if}}, {{#unless}}, {{#each}}, {{#with}}{{else}} within conditionals{{#if premium_member}}
Welcome back, valued member!
{{else}}
Consider upgrading to premium for more features.
{{/if}}
{{#unless email_verified}}
Please verify your email to continue.
{{/unless}}
Your items:
{{#each cart.items}}
- {{this.name}}: {{this.price}}
{{/each}}
Special variables available inside #each:
{{@index}} - Current iteration index (0-based){{@first}} - True if first item{{@last}} - True if last itemExample using @last to add commas between items:
{{#each items}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}
Renders as: a, b, c
{{#with user.address}}
{{street}}, {{city}} {{zip}}
{{/with}}
UniversalChatbot provides built-in helper functions for common operations. Use them directly or combine with #if for conditional logic.
| Helper | Usage | Output |
|---|---|---|
| upcase | {{upcase name}} | JOHN |
| downcase | {{downcase name}} | john |
| capitalize | {{capitalize name}} | John |
| titleize | {{titleize title}} | The Quick Fox |
| truncate | {{truncate text "20"}} | This is a long te... |
| default | {{default name "Guest"}} | Guest (if empty) |
Use with #if for conditional rendering. Literal values must be quoted.
| Helper | Usage | Description |
|---|---|---|
| eq | {{#if (eq status "active")}} | Equal to |
| ne | {{#if (ne status "banned")}} | Not equal to |
| gt | {{#if (gt count "5")}} | Greater than |
| gte | {{#if (gte count "5")}} | Greater than or equal |
| lt | {{#if (lt count "5")}} | Less than |
| lte | {{#if (lte count "5")}} | Less than or equal |
| Helper | Usage | Description |
|---|---|---|
| and | {{#if (and premium verified)}} | Both must be true |
| or | {{#if (or admin moderator)}} | Either can be true |
| not | {{#if (not banned)}} | Invert the value |
| Helper | Usage | Description |
|---|---|---|
| size | {{size items}} | Returns array length |
| contains | {{#if (contains tags "vip")}} | Check if array has item |
| join | {{join items ", "}} | Join array elements |
| is_empty | {{#if (is_empty items)}} | Check if nil or empty |
| is_present | {{#if (is_present items)}} | Check if has values |
| Helper | Usage | Result |
|---|---|---|
| add | {{add price tax}} | 100 + 10 = 110 |
| subtract | {{subtract total discount}} | 100 - 20 = 80 |
| multiply | {{multiply quantity price}} | 5 * 20 = 100 |
| divide | {{divide total count}} | 100 / 4 = 25 |
{{json object}} - Outputs value as JSON stringHelpers can be nested using subexpressions (parentheses):
// Uppercase with fallback
Hello {{upcase (default name "guest")}}!
// Check if array has many items
{{#if (gt (size items) "5")}}
You have many items in your cart.
{{/if}}
// Combined conditions
{{#if (and (gt age "18") verified)}}
You're eligible for this offer.
{{/if}}
Note: Literal values in helper calls must be quoted: use "5" not 5. Variable references don't need quotes.
Limitations
items.0, items[0] - use #each instead{{! comment }} syntax is not supported"5" not 5 in helper calls{{> partialName}} partial includes{{{{raw}}}}...{{{{/raw}}}}{{helper key=value}} named parametersUnlike standard Handlebars, our implementation does not HTML-escape output since we're working with plain text messages, not HTML. This means {{variable}} and {{{variable}}} produce the same result.
Since array index access isn't supported, use these approaches instead:
{{#each api_response.body.items}}
Item: {{this.name}} - {{this.price}}
{{/each}}
Instead of returning nested arrays requiring index access, configure your APIs to return the specific data you need:
response.orders.0.items:// Configure API to return current_order directly
{{current_order.items}}
Within an #each block, you can access the current index using @index:
{{#each products}}
{{@index}}. {{this.name}} - ${{this.price}}
{{/each}}
Hi {{customer_name}}! Thanks for reaching out about your {{inquiry_type}}.
I can see you're contacting us on {{@today}}. Let me help you with that.
https://api.example.com/orders/{{order_id}}/status
{
"customer_id": "{{customer_id}}",
"email": "{{email_address}}",
"requested_at": "{{@now}}",
"platform": "{{@platform}}"
}
{{#if order_status.body.shipped}}
Great news! Your order has shipped and should arrive by {{order_status.body.estimated_delivery}}.
{{else}}
Your order is being prepared and will ship soon.
{{/if}}
Here are the products matching your search:
{{#each search_results.body.products}}
{{@index}}. {{this.name}} - ${{this.price}}
{{/each}}