Workflow Studio

Expression Language

Template expressions, JSONata transforms, built-in functions, and the full variable context available inside workflow nodes.

Overview

FlowOS uses a dual expression system: Mustache-style templates ({{...}}) for simple value interpolation in node config strings, and JSONata for complex data transformation in Transform nodes. Both have access to the same runtime context.

Template Syntax

Wrap any JSONata path expression in double curly braces to interpolate its value into a string field:

json
// Simple value
"to": "{{trigger.record.email}}"

// Nested path
"body": "Incident {{trigger.record.number}} assigned to {{trigger.record.assignedTo.name}}"

// Arithmetic
"ttl": "{{nodes.computeTtl.output.hours * 3600}}"

// Conditional (ternary)
"priority": "{{trigger.record.severity = 'P1' ? 'urgent' : 'normal'}}"

// Array index
"firstOwner": "{{trigger.record.cmdbItems[0].ownerEmail}}"

// String concatenation
"subject": "{{'[' & trigger.record.severity & '] ' & trigger.record.title}}"

Runtime Variable Context

The following variables are available in all template expressions and JSONata transforms:

trigger.*

FieldTypeRequiredDefaultDescription
trigger.recordobjectoptionalFor record_event triggers: the full record (after change for updates).
trigger.beforeobjectoptionalFor record_updated: field values before the change.
trigger.changesobject[]optionalFor record_updated: [{ field, from, to }] array of changed fields.
trigger.bodyobjectoptionalFor webhook triggers: parsed request body.
trigger.headersobjectoptionalFor webhook triggers: request headers (lowercased keys).
trigger.eventobjectoptionalFor itsm_event and event_bus triggers: the full event payload.
trigger.scheduledAttimestampoptionalFor schedule triggers: intended fire timestamp.
trigger.inputobjectoptionalFor manual triggers: the input object passed to the trigger call.

nodes.*

FieldTypeRequiredDefaultDescription
nodes.NODE_ID.outputobjectoptionalThe output of a completed node. Only accessible from nodes that run after the referenced node.
nodes.NODE_ID.statusstringoptionalcompleted · failed · skipped. Useful in condition expressions.
nodes.NODE_ID.errorobjectoptionalError details if the node failed: { code, message }.
nodes.NODE_ID.durationMsintegeroptionalHow long the node took to execute.

vars.*

FieldTypeRequiredDefaultDescription
vars.KEYanyoptionalVariables set by set_variable nodes. Mutable across the run.

run.*

FieldTypeRequiredDefaultDescription
run.idstringoptionalCurrent run ID (run_...).
run.workflowIdstringoptionalWorkflow ID that owns this run.
run.startedAttimestampoptionalWhen this run started executing.
run.attemptintegeroptionalAttempt number (1 = first run, 2+ = retries).

workspace.*

FieldTypeRequiredDefaultDescription
workspace.idstringoptionalWorkspace ID.
workspace.slugstringoptionalWorkspace slug.
workspace.namestringoptionalWorkspace display name.
workspace.config.KEYanyoptionalWorkspace configuration values set under Settings → Config.

secrets.*

Secrets are write-once, read-in-node. They are resolved at runtime and never appear in logs or traces.

FieldTypeRequiredDefaultDescription
secrets.SECRET_NAMEstringoptionalValue of a secret stored in the vault. Case-sensitive. Never logged.

JSONata Reference

JSONata is used in Transform nodes and condition expressions. The full JSONata specification is at jsonata.org. Key patterns for FlowOS:

Path navigation

text
trigger.record.title                    // string property
trigger.record.tags[0]                  // first array element
trigger.record.cmdbItems.ownerEmail     // extract field from each array element
trigger.record.cmdbItems[class='server'] // filter array

Constructing objects and arrays

text
// Build a new object
{
  "summary":   trigger.record.title,
  "severity":  trigger.record.severity,
  "owners":    trigger.record.cmdbItems.ownerEmail[],  // force array
  "isP1":      trigger.record.severity = 'P1'
}

// Map array to new shape
trigger.record.cmdbItems.{
  "name":  name,
  "owner": ownerEmail,
  "class": class
}

String functions

text
$string(value)              // cast to string
$uppercase(str)             // "hello" → "HELLO"
$lowercase(str)             // "HELLO" → "hello"
$trim(str)                  // strip whitespace
$contains(str, pattern)     // true/false, pattern can be regex
$replace(str, from, to)     // replace occurrences
$split(str, separator)      // split to array
$join(array, separator)     // join array to string
$length(str)                // string length
$substring(str, start, len) // substring extraction

Numeric functions

text
$sum(array)       // sum numeric array
$min(array)       // minimum value
$max(array)       // maximum value
$average(array)   // arithmetic mean
$round(n, dp)     // round to decimal places
$floor(n)         // round down
$ceil(n)          // round up
$abs(n)           // absolute value
$formatNumber(n, picture)   // e.g. $formatNumber(1234.5, '#,###.00') → "1,234.50"

Array functions

text
$count(array)           // number of elements
$append(a1, a2)         // concatenate arrays
$sort(array, fn)        // sort; optional comparator
$reverse(array)         // reverse order
$distinct(array)        // remove duplicates
$zip(a1, a2)            // merge two arrays by index
$filter(array, fn)      // filter: $filter(items, function($v){ $v.active })
$map(array, fn)         // transform: $map(items, function($v){ $v.name })
$reduce(array, fn, init)// accumulate

Date/time functions

text
$now()                          // current UTC timestamp ISO 8601
$millis()                       // current time in milliseconds
$fromMillis(ms)                 // ms → ISO 8601 string
$toMillis(timestamp)            // ISO 8601 → ms
$dateTime(timestamp, picture)   // format: $dateTime($now(), '[M01]/[D01]/[Y0001]')

Conditional and logic

text
// Ternary
condition ? trueValue : falseValue

// Null coalescing
value ~> $default('fallback')

// Boolean operators
a and b
a or b
not condition

// Comparison
= (equals), != (not equals)
< > <= >=
in (array membership): "P1" in ["P1","P2"]

Real-World Examples

Route based on SLA breach percentage

json
{
  "type": "condition",
  "config": {
    "expression": "(($toMillis($now()) - $toMillis(trigger.record.createdAt)) / (trigger.record.sla.resolutionTarget * 1000)) * 100 >= 75"
  }
}

Build a Slack message with incident details

json
{
  "action": "slack.postMessage",
  "config": {
    "input": {
      "channel": "#incidents",
      "text": "*[{{trigger.record.severity}}] {{trigger.record.title}}*
Assigned: {{trigger.record.assignedTeam}}
SLA due: {{trigger.record.sla.resolutionDue}}
<https://acme.flowos.io/itsm/{{trigger.record.id}}|View Incident>"
    }
  }
}

Filter and reshape CMDB items for downstream use

json
{
  "type": "transform",
  "config": {
    "expression": "trigger.record.cmdbItems[class='server'].{ 'host': hostname, 'ip': ipAddresses[0], 'owner': ownerEmail }",
    "outputAs": "affectedServers"
  }
}
// Access as: {{nodes.TRANSFORM_NODE_ID.output.affectedServers}}