Appearance
Prompt Templating
Prompts in Bonsai are not just static text — they're templates that can include dynamic content based on the conversation state. This is powered by the Handlebars templating language.
You'll encounter templates in stage prompts, agent prompts, tool prompts, and several action effects.
Inserting Dynamic Values
Use double curly braces {{ }} to insert a value:
Available Data
| Variable | Description |
|---|---|
vars | Current stage variables (data collected during the conversation) |
stageVars | Variables from all stages, keyed by stage ID — e.g. {{stageVars.stage-id.field}} |
userProfile | The end user's profile information (custom fields defined in the project) |
consts | Project-level constants (company name, support hours, etc.) |
userInput | What the user just said |
time | Current date and time (timezone-aware) |
project | Project-level settings exposed at runtime (timezone, languageCode, language) |
context.results | Results from tool calls and webhooks |
{{agent}} | The agent's personality prompt — must be explicitly included (see below) |
faq | Array of matched Q&A pairs — must be explicitly included via {{#hasItems faq}} (see below) |
copy | Selected sample copy content (decorated), or empty string if no copy matched — requires {{copy}} in the prompt to activate (see below) |
copyContent | Raw selected sample copy content before the decorator is applied |
sampleCopy | Array of all active sample copies for this stage, each with name, trigger, and content fields |
User Profile Fields
Custom fields can be declared per-project in Design > Global Memory > User Profile tab. Custom fields show up in the prompt editor's autocomplete and can be accessed the same way:
See Projects for details on defining custom fields.
Cross-Stage Variables
To read variables collected in a different stage, use stageVars with the stage's ID:
This is useful when a later stage needs data that was captured earlier without duplicating variable definitions.
Nested Properties
Access nested data with dots:
Conditional Content
Show content only when a condition is met:
Checking if a Value Exists
The exists helper is safer than #if because it specifically checks for null or undefined:
Checking Array Length
Loops
Iterate over arrays with #each:
Useful Helpers
Bonsai includes several built-in helpers beyond standard Handlebars:
| Helper | What It Does | Example |
|---|---|---|
exists | Show content only if a value is defined | {{#exists vars.name}}...{{/exists}} |
hasItems | Show content only if an array has items | {{#hasItems vars.orders}}...{{/hasItems}} |
get | Safely access deeply nested values | {{get vars "customer.address.city"}} |
join | Join array elements with a separator | {{join vars.sizes ", "}} |
contains | Check if an array contains a value | {{#contains vars.features "premium"}}...{{/contains}} |
default | Provide a fallback if a value is missing | {{default userProfile.name "valued customer"}} |
json | Output a value as JSON | {{json vars}} |
Time Context
Every prompt has access to a time object with the current date and time, anchored to the conversation's timezone. This is especially useful for scheduling, booking, and any time-sensitive scenarios.
Quick Start — Grounding the AI in Time
Add this at the top of any prompt to give the AI accurate awareness of the current date and time:
This produces a single sentence like:
Today is Friday, 27 February 2026 (Europe/Warsaw, UTC+01:00). This week (Mon–Sun): 23 Feb–1 Mar. Next week: 2 Mar–8 Mar.
Common Time Fields
| Field | Example | Description |
|---|---|---|
time.date | 2026-02-27 | Current date |
time.time | 14:30:00 | Current time (24-hour) |
time.dayOfWeek | Friday | Day of the week |
time.timezone | Europe/Warsaw | Active timezone |
time.nextMonday | 2026-03-02 | Date of next Monday |
time.nextTuesday | 2026-03-03 | Date of next Tuesday |
time.calendar | (array) | Next 14 days with day names and dates |
Timezone Priority
The timezone is determined once when a conversation starts:
- Timezone passed in the connection message (highest priority)
- Timezone stored in the user's profile
- Project-level timezone setting
- UTC (fallback)
Example — Booking Assistant
Project Context
The project object exposes project-level settings in every conversation:
| Field | Description |
|---|---|
project.timezone | IANA timezone configured on the project — e.g. "Europe/Warsaw". null if not set. |
project.languageCode | ISO language code configured on the project — e.g. "en-US" or "pl-PL". null if not set. |
project.language | Human-readable language name derived from languageCode — e.g. "American English". null if not set. |
Example usage in a prompt:
Agent and Knowledge Variables
Two variables have behavior that is easy to get wrong: {{agent}} and {{faq}}.
{{agent}} — Agent Personality
The {{agent}} tag injects the agent's personality prompt — the description and instructions defined on the Agent resource — into your stage system prompt.
Must be explicitly included
{{agent}} is not auto-injected. If you omit it from your stage prompt, the agent's personality is silently absent and the LLM has no personality instructions at all. Always include {{agent}} unless you are intentionally overriding the personality inline.
Typical placement is near the top of the prompt:
{{faq}} — Knowledge Base Results
When a stage has Use Knowledge enabled and the classifier matches a knowledge category, the matched FAQ entries are made available as faq — an array of objects with question and answer fields. You must iterate over it explicitly in your prompt.
Must be explicitly included
faq is not auto-injected. If you omit the faq block from your stage prompt, matched knowledge results are silently discarded — the LLM will never see them, even though the classifier found relevant content. Always include a {{#hasItems faq}} block on stages where knowledge lookup is enabled.
Use #hasItems to guard against rendering when no knowledge was matched, and #each to iterate the entries:
Sample Copy Variables
To enable sample copy injection on a stage, include {{copy}} in the stage prompt. When the sample copy classifier matches a copy, the following variables are populated:
| Variable | Type | Description |
|---|---|---|
{{copy}} | string | The selected copy content with any decorator applied, joined by newlines. Empty string if no copy was matched. |
{{copyContent}} | string | The raw selected content before any decorator template is applied. Same as copy when no decorator is set. |
{{sampleCopy}} | SampleCopyItem[] | All sample copies active for this stage, each with name, trigger, and content fields. |
is empty, not missing
When no sample copy is matched, {{copy}} is an empty string rather than undefined. Use an #if guard so the surrounding instruction text is only shown when content is actually available:
See Sample Copies for a full guide on creating and configuring sample copies and decorators.
Full Prompt Example
Here's a realistic stage prompt combining several template features:
Tips
- Use
existsguards around variables that might not be set yet. - Put shared values in project constants (like your company name) instead of repeating them in every prompt. Access them as
{{consts.key}}. - Use
stageVarsto reference data collected in earlier stages without duplicating variable definitions. - Use variables to build up context across conversation turns — the prompt can reference everything collected so far.
- Test with edge cases — think about what happens when a variable is empty or hasn't been set.