Skip to Content
CraftBot Docs v1 — internal preview
Recipes & Bot EngineVariables & expressions

Variables & expressions

Variables are how data moves between nodes in a recipe. A Question writes the customer’s reply into a variable; a Message reads variables back out via {{name}} syntax; a Condition evaluates a rule against variables.

Scopes

Every variable lives in one of four scopes. The scope decides how long the value sticks around and who else can see it.

ScopeLifetimeTypical use
RecipeThe current recipe activation. Cleared when the recipe ends or returns from a sub-flow.Scratch values inside a single recipe — counters, temporary flags.
ConversationThe whole flow session, across sub-flow calls.Data the customer provides early that downstream nodes need to read — prescription ID, order number, branch selection.
UserPersists on the contact, across conversations.Long-lived attributes that follow the customer — preferred language, VIP status, plan tier.
ConfigRead-only tenant configuration set in Settings.API keys, secrets, endpoint URLs that an API node needs but you don’t want hardcoded into the recipe.

The runtime also exposes a small set of system variables — synthetic values like the customer’s phone, the current conversation ID, and the active language. These are read-only and described below.

Setting

Variables are set by:

  • Question / Typed Input / Buttons / List / Form nodes — write the customer’s reply into a configured variable.
  • Set Variable node — explicit assignment with a literal value or another variable reference.
  • API node — the response-mapping section maps JSONPath expressions on the HTTP response into named variables.
  • Sub-flow Call — the output mapping lifts return values from a sub-flow into the parent’s scope.

When a Set Variable or input node has its target scope set to User, the value is also written to the contact’s profile and is visible the next time the same contact starts a conversation.

Reading

Anywhere a field supports interpolation (message bodies, API URLs, headers, request bodies, condition expressions), reference variables with the double-brace syntax:

Hi {{user.name}}, your prescription {{rx_id}} is being prepared.

The reference syntax follows the scope of the variable you want to read:

You want…Write
Conversation-scoped variable named rx_id.{{rx_id}} — bare names default to conversation scope.
Recipe-scoped variable.{{recipe_var.counter}} or the short form {{r.counter}}.
Contact-scoped variable (or any custom contact attribute).{{contact.tier}} or {{user.tier}}.
Config-scoped value from tenant settings.{{config.api_key}}.
A system value (see table below).{{user.phone}}, {{conversation.id}}, etc.

Unknown references render as an empty string and emit an unknown_variable_referenced audit event so you can spot typos in published recipes.

Filters

Append filters with | to transform a value during interpolation. The runtime ships these built-ins; unknown filter names pass through silently.

FilterWhat it doesExample
lowerLower-case a string.{{contact.name | lower}}
upperUpper-case a string.{{rx_id | upper}}
trimStrip leading and trailing whitespace.{{user.email | trim}}
default:'value'Substitute a fallback when the value is empty, null, or undefined.{{contact.name | default:'there'}}
date:'pattern'Format a date/ISO timestamp. Tokens: YYYY MM DD HH mm ss.{{conversation.created_at | date:'YYYY-MM-DD HH:mm'}}

You can chain filters: {{contact.name | trim | lower | default:'guest'}}.

System variables

These are resolved synthetically by the runtime — there’s nothing to declare; they’re always available.

VariableValue
{{user.name}}The contact’s display name (from the channel profile).
{{user.phone}}The contact’s phone number in E.164.
{{user.email}}The contact’s email, when known.
{{conversation.id}}The current conversation UUID.
{{recipe.id}}The recipe ID running this session.
{{recipe.version_id}}The exact published version running this session.
{{language}}The active session language — en or ar.

Inspecting at runtime

The simulator’s Variables panel lists every variable in scope at every step of a test session — useful for debugging flows where the wrong branch keeps firing. See The simulator.

Notes & gotchas

  • Path depth is capped at four{{a.b.c.d.e}} is rejected. Keep nested object access shallow.
  • Recipe scope is per-recipe activation, not per-session — when a sub-flow is called, the parent’s recipe-scoped variables are not visible inside the sub-flow. Use conversation scope or explicit sub-flow input mappings for cross-recipe data.
  • Class A (PHI) and Class B (PII) writes are audited without the value. The variable_set audit row records the variable name, scope, and classification but redacts the value to satisfy PDPL. Class C (internal) values are logged in full.
  • There is no expression language{{count + 1}} will not work. Use a Set Variable node to compute a new value, or do the math externally via API.
Last updated on