Data and variables
The language of workflow data. Covers /#steps path references and {{{ }}} Handlebars templates side by side, what each produces and when to use it, every path you can address (trigger output, changes, step outputs, variables by ID, /#error, callable inputs), path syntax, missing values, type conversion, common helpers, escaping, and how builder picks translate to JSON.
Every value in a workflow is one of three things: a literal such as "done" or 4, a path reference such as "/#steps.1.status", or a template such as "Order {{{steps.1.custom.orderNumberStr}}}". References and templates read from the same place, the run's context, but they behave differently. Knowing which one to use, and what each one produces, is most of what you need to write workflows confidently.
This page is the grammar. For condition operators, see Conditions and variables. For every function and helper, see the Expressions reference.
Two syntaxes
Both forms write the path the same way, steps.1.tags; only the wrapping differs. Here they are in one step:
{
"id": "note",
"action": "kustomer.note.create",
"params": {
"conversation": "/#steps.1.id",
"body": "Order {{{steps.1.custom.orderNumberStr}}}: {{{steps.find.name}}}"
},
"transitions": []
}conversation needs an ID, so it's a reference to one. body mixes text and values, so it's a template.
A reference is the whole value
A string is a reference only when /# is the very first thing in it. There's no way to embed a reference in other text: use a template.
Templates produce strings
A template is rendered to text, whatever the values inside it are. Each param is then converted to the type the action's input expects, so a template that renders "4" for a number input becomes 4. That conversion only goes so far, so pick the syntax by the type you need:
Arrays and objects don't survive templates. A template prints an array's items joined with commas, so {{{steps.1.tags}}} renders "64f1c0ffee5ca1ab1e000001,64f1c0ffee5ca1ab1e000002", and it prints an object as [object Object]. To put an object into text, such as a log message, use {{{toJSON steps.find.custom}}}.
Double or triple braces
{{ }} HTML-escapes the value. {{{ }}} inserts it unchanged.
Notes, URLs, headers and JSON bodies all need the original text, so use triple braces everywhere unless you're building HTML and want the escaping.
Try it
Pick a value, type your own, or click a line of the context. Each one is resolved three ways, as a reference and as both kinds of template, so you can see how the type changes.
The trigger is always step 1, and its record is flattened, so attributes sit at the top level.
What you can address
A path starts at the top of the run's context:
Trigger output: steps.1
A record trigger's output is the record, flattened: its attributes sit at the top level, relationships are plain IDs, and update triggers add a changes diff. The full example is in What the trigger outputs.
Other triggers put their data in different places:
- Inbound web hooks put your payload under
attributes.data, so a field calledemailis/#steps.1.attributes.data.email. Paste a sample payload into the trigger's Example Webhook Data so its fields appear in the builder's picker. - Callable workflows get only their inputs, cast to the trigger's
schema. An input calledconversationIdis/#steps.1.conversationId, not/#steps.1.inputs.conversationId.
What changed: changes
Update triggers include a changes diff with each changed attribute's before and after. Custom attributes change as one custom entry, so the field name comes after before or after:
Gate update triggers on changes rather than on the current value. See Conditions on what changed.
Step outputs: steps.<id>
A step's output is exactly what the builder's View Logs shows for it. When a value comes out empty, compare your path with the logged output. See Debugging.
Workflow variables
Workflow variables hold secrets and settings that shouldn't be written into the workflow, such as an API token. Reference a variable by its ID, not its name. The ID is the 24-character data.id returned when you create the variable.
{
"headers": {
"Authorization": "Bearer {{{variables.6513b2e4a4b1e1001a6b3f10}}}",
"X-Api-Key": "/#variables.6513b2e4a4b1e1001a6b3f11"
}
}The name is only a label. A reference by name resolves to nothing, so the header above would be sent as "Bearer ". You rarely need to type IDs yourself:
- In the builder, picking a variable from a field's picker inserts
/#variables.<variableId>. - In an object field, such as a REST step's headers, the Variables button copies
{{{variables.<variableId>}}}for you to paste. - With the API, list the workflow's variables to find each one's ID.
Values are strings of 1 to 1,024 characters. The builder and the API show them masked, with only the last 6 characters visible, so they can't be read back.
Each variable belongs to one workflow. If you copy a workflow's JSON into another workflow or another org, every variable reference breaks until you create the variables there and update the IDs. Kustomer loads variables only when the workflow refers to them, so always write /#variables. or {{{variables. out in full.
The error: /#error
Inside a step's error cases, /#error is the error that step just raised. For a Kustomer action, it's the first item of the API's errors array:
/#error exists only while error cases are checked. It's never at /#steps.<id>.error, and it's gone once the run moves on.
App settings
Workflows that ship in an app read the installing org's settings with the setting helper, in templates only: {{{ setting <app>.<category>.<name> }}}. It can also take a default and a type: {{{ setting <app>.<category>.<name> <default> <type> }}}. See App settings.
Path syntax
- Indexes are whole numbers from
0. There are no negative indexes, so there's no "last item" shortcut. - Only the object's own keys are read.
- In templates, use dots for indexes:
{{{steps.find.emails.0.email}}}. - Function arguments are bare paths, without
/#:/#fn:parseInt,steps.1.custom.qtyStr, not/#fn:parseInt,/#steps.1.custom.qtyStr. - Give steps IDs without dots, such as
find-customer. Transition targets can't contain dots.
Missing values
A path that doesn't resolve never causes an error. What you get instead depends on where the path is used:
A typo in a step ID or field name looks exactly like missing data. To handle values that really can be missing:
- Guard with a condition. Put
exists [/#steps.find.id]on the transition into the step that needs it. - Give templates a fallback.
{{{or steps.find.firstName 'there'}}}prints the first name, ortherewhen there isn't one. - Leave optional params out.
/#fn:ifExists,steps.find.id,/#steps.find.nameresolves to the name only when the lookup found someone.
Types and conversion
After a param is resolved, it's converted to the type of the action's input:
Use functions when the source has the wrong type, such as a number stored as text in a web hook payload:
An unknown function name doesn't fail. It returns the input unchanged, so check the spelling against the Expressions reference. Conditions do their own casting: eq converts the right side to the left side's type. See Operators.
Handlebars helpers you'll use most
Helpers go inside templates, with positional arguments first and then key=value options. Arguments are paths, single-quoted strings or numbers.
Conditions accept templates as well as references, which is how you make a text check case-insensitive:
{ "op": "contains", "values": ["{{{toLower steps.1.subject}}}", "refund"] }The full list, with every option and default, is in the Expressions reference.
Escaping
Three layers can change your text: JSON, Handlebars and URLs.
- JSON. A workflow is a JSON document, so backslashes and double quotes in strings need escaping: a regex
\d+is written"\\d+". Helper arguments use single quotes,format='LL', so they don't need escaping. - HTML.
{{ }}HTML-escapes values. Use{{{ }}}. - URLs. A value that goes into a URL needs encoding:
"https://api.example.com/customers?email={{{uriEncode steps.1.attributes.data.email}}}". Otherwise a space or&breaks the request.
To send JSON to your own service, give the REST step's data an object, and use references for the values. Each value keeps its type, and the step serializes the body for you. Don't build a JSON body as a template string.
{
"data": {
"conversationId": "/#steps.1.id",
"priority": "/#steps.1.priority",
"tags": "/#steps.1.tags",
"summary": "{{{steps.find.name}}}: {{{steps.1.name}}}"
}
}From the builder to JSON
The builder writes all of this for you. Knowing what it writes lets you read any workflow's JSON, and fix it when the picker can't express what you need.
Code view text is committed when you press Enter or click away, and it's converted to the field's type at that point.