Skip to content

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:

handlebars
Hello {{userProfile.name}}, welcome to {{consts.companyName}}.

Available Data

VariableDescription
varsCurrent stage variables (data collected during the conversation)
stageVarsVariables from all stages, keyed by stage ID — e.g. {{stageVars.stage-id.field}}
userProfileThe end user's profile information (custom fields defined in the project)
constsProject-level constants (company name, support hours, etc.)
userInputWhat the user just said
timeCurrent date and time (timezone-aware)
projectProject-level settings exposed at runtime (timezone, languageCode, language)
context.resultsResults from tool calls and webhooks
{{agent}}The agent's personality prompt — must be explicitly included (see below)
faqArray of matched Q&A pairs — must be explicitly included via {{#hasItems faq}} (see below)
copySelected sample copy content (decorated), or empty string if no copy matched — requires {{copy}} in the prompt to activate (see below)
copyContentRaw selected sample copy content before the decorator is applied
sampleCopyArray 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:

handlebars
{{userProfile.tier}}
{{userProfile.accountId}}
{{userProfile.preferences.language}}

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:

handlebars
{{stageVars.identify-customer.email}}
{{stageVars.order-lookup.orderId}}

This is useful when a later stage needs data that was captured earlier without duplicating variable definitions.

Nested Properties

Access nested data with dots:

handlebars
Your order {{vars.order.id}} is currently {{vars.order.status}}.

Conditional Content

Show content only when a condition is met:

handlebars
{{#if vars.customerName}}
I see your name is {{vars.customerName}}.
{{else}}
Could you tell me your name?
{{/if}}

Checking if a Value Exists

The exists helper is safer than #if because it specifically checks for null or undefined:

handlebars
{{#exists vars.orderNumber}}
Let me look up order {{vars.orderNumber}} for you.
{{/exists}}

Checking Array Length

handlebars
{{#hasItems vars.pendingOrders}}
You have {{vars.pendingOrders.length}} pending orders.
{{/hasItems}}

Loops

Iterate over arrays with #each:

handlebars
Here are the steps we've completed:
{{#each vars.completedSteps}}
- {{this}}
{{/each}}

Useful Helpers

Bonsai includes several built-in helpers beyond standard Handlebars:

HelperWhat It DoesExample
existsShow content only if a value is defined{{#exists vars.name}}...{{/exists}}
hasItemsShow content only if an array has items{{#hasItems vars.orders}}...{{/hasItems}}
getSafely access deeply nested values{{get vars "customer.address.city"}}
joinJoin array elements with a separator{{join vars.sizes ", "}}
containsCheck if an array contains a value{{#contains vars.features "premium"}}...{{/contains}}
defaultProvide a fallback if a value is missing{{default userProfile.name "valued customer"}}
jsonOutput 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:

handlebars
{{time.anchor}}

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

FieldExampleDescription
time.date2026-02-27Current date
time.time14:30:00Current time (24-hour)
time.dayOfWeekFridayDay of the week
time.timezoneEurope/WarsawActive timezone
time.nextMonday2026-03-02Date of next Monday
time.nextTuesday2026-03-03Date 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:

  1. Timezone passed in the connection message (highest priority)
  2. Timezone stored in the user's profile
  3. Project-level timezone setting
  4. UTC (fallback)

Example — Booking Assistant

handlebars
{{time.anchor}}

You are a booking assistant for {{consts.companyName}}.
When the user says "next Tuesday", that means {{time.nextTuesday}}.
When the user says "this Friday", that means {{time.nextFriday}}.

Project Context

The project object exposes project-level settings in every conversation:

FieldDescription
project.timezoneIANA timezone configured on the project — e.g. "Europe/Warsaw". null if not set.
project.languageCodeISO language code configured on the project — e.g. "en-US" or "pl-PL". null if not set.
project.languageHuman-readable language name derived from languageCode — e.g. "American English". null if not set.

Example usage in a prompt:

handlebars
{{#exists project.language}}
Please respond in {{project.language}}.
{{/exists}}

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:

handlebars
{{agent}}

{{time.anchor}}

You are helping the customer with their order.

{{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:

handlebars
{{#hasItems faq}}
Relevant knowledge:
{{#each faq}}
Q: {{this.question}}
A: {{this.answer}}
{{/each}}
{{/hasItems}}

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:

VariableTypeDescription
{{copy}}stringThe selected copy content with any decorator applied, joined by newlines. Empty string if no copy was matched.
{{copyContent}}stringThe 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:

handlebars
{{agent}}

You are a customer service agent.

{{#if copy}}
Use the following prescripted answer for this question:
{{copy}}
{{/if}}

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:

handlebars
{{time.anchor}}

You are a {{consts.agentRole}} for {{consts.companyName}}.

{{#exists vars.customerName}}
You are speaking with {{vars.customerName}}.
{{/exists}}

{{#exists vars.issue}}
Current issue: {{vars.issue}}
Steps completed so far:
{{#hasItems vars.steps}}
{{#each vars.steps}}
- {{this}}
{{/each}}
{{/hasItems}}
{{/exists}}

Always be polite and professional. If you cannot resolve the issue,
offer to transfer to a human agent.

Tips

  • Use exists guards 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 stageVars to 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.

Released under the Apache-2.0 License.