Expressions
Helios uses JavaScript expressions to dynamically generate and transform values from the data available in and out of the workflow.
Anything inside double curly braces {{ }}
is evaluated as JavaScript code. They can be used as standalone expressions:
{{ 1 + 2 }}
{{ Math.max(10, 20, 30) }}
{{ JSON.stringify({ key: "value" }) }}
or can be used within strings:
"Hello {{ node.nameGetter.output.name }}! The current time is {{ new Date().toISOString() }}"
Context Variables
Expressions have access to the following context variables that provide data from your workflow:
Trigger Data
Every workflow run starts from a trigger. The trigger data is available in the trigger
variable. The shape of the object depends on the trigger type as shown below.
{
"type": "cron",
"triggerId": "trigger_12345",
"timestamp": "2024-01-15T10:30:00.000Z",
"payload": ... // Any valid JSON value
}
Example usage
{{ trigger.type === "cron" ? "This is a cron trigger" : "This is not a cron trigger" }}
{{ trigger.type === "manual" ? `Triggered by: ${trigger.triggeredByUser}` : "Not a manual trigger" }}
{{ trigger.payload?.user?.email || "No email provided" }}
{{ trigger.type === "integration" ? trigger.integrationId : trigger.triggerId }}
{{ trigger.payload.items.filter(item => item.status === "active").map(item => item.name).join(", ") }}
Node runs
All running and completed node runs are available in the nodes
context variable. It is an object with the node label as the key and the node run as the value:
{
"ai_1": NodeRun,
"node-67890": NodeRun,
myOtherNode: NodeRun,
myGitHubNode: NodeRun
}
Each NodeRun
object is described according to its status:
{
"status": "succeeded",
"id": "run-12345",
"nodeId": "node-67890",
"runAttempt": 1,
"startedAt": "2024-01-15T10:30:00.000Z",
"endedAt": "2024-01-15T10:31:30.000Z",
"input": ... // Node specific input,
"configuration": {
"timeoutMs": 15000,
"maxRetries": 0,
"errorHandling": "stop" // or "continue",
... // Other node specific configuration
},
"output": ... // Node specific output
}
You can find the specific input
, configuration
, and output
properties for each node in the specific node documentation.
Example usage
{{ nodes.httpRequest.output.data }}
{{ nodes.jsonParser.output.parsed }}
{{ nodes.filter.output.items }}
{{ nodes['node-1'].output.result }} // Accessing nodes with hyphenated labels
{{ nodes['node name'].output.items[1] }} // Accessing nodes with spaces in the label
{{ nodes['node-2'].status }}
{{ nodes['node-2'].error.message }}
Availability
You can technically access any node via nodes.nodeLabel
but beware that the node run may not be available yet.
For example, a parent node trying to access a child or a concurrently running node trying to access a sibling that hasn't started or finished yet.
In these cases, the node run will be undefined
and will fail accordingly.
{{ nodes.notYetStartedNode.output.data }} // This will fail
{{ nodes.notYetStartedNode?.output?.data }} // This will return undefined
Secrets and Variables
Any secrets and variables accessible to the workflow are available in the secrets
and vars
context variables respectively.
{{ vars.counter }}
{{ vars.userEmail }}
{{ vars.config.environment }}
{{ secrets.GITHUB_PERSONAL_ACCESS_TOKEN }}
{{ secrets.PREPROD_DATABASE_URL }}
Loop Context
Nodes inside loops have access to iteration-specific data via the loop
context variable:
{
"loop": {
"currentIteration": {
"item": // The current item in the loop,
"nodes": NodeRun[] // all the node runs in the loop,
},
// The total number of iterations in the loop.
// If an array was passed, this will be the length of the array.
// If a number was passed, this will be the number of iterations.
"totalIterations": 10,
}
}
Example usage
{{ loop.currentIteration.item.data }}
{{ loop.currentIteration.nodes.previousNode.output }}
{{ loop.totalIterations }}
Available Global Objects
Expressions have access to the following standard JavaScript global objects:
-
Math
- MDN Docs{{ Math.max(1, 2, 3) }} {{ Math.floor(3.7) }} {{ Math.random() }}
-
Date
- MDN Docs{{ new Date().toISOString() }} {{ new Date().getTime() }}
-
Array
- MDN Docs{{ Array.from({ length: 10 }, (_, i) => i) }} {{ Array.isArray([1, 2, 3]) }}
-
JSON
- MDN Docs{{ JSON.stringify({ name: "John" }) }} {{ JSON.parse('{"key": "value"}') }}
-
Number
- MDN Docs{{ Number.parseInt("123") }} {{ Number.parseFloat("123.45") }}
Examples
// Combine data from two parent nodes
{{ { ...nodes.userProfile.output.data, ...nodes.userPreferences.output.data } }}
// Get user's full name from profile data
{{ `${nodes.userProfile.output.data.firstName} ${nodes.userProfile.output.data.lastName}` }}
// Use fallback value if data is missing
{{ nodes.userProfile.output.data?.nickname ?? nodes.userProfile.output.data?.firstName }}
// Check if both nodes completed successfully
{{ nodes.userProfile.status === "succeeded" && nodes.userPreferences.status === "succeeded" }}
// Filter active items from a data source
{{ nodes.dataFetcher.output.items.filter(item => item.status === "active") }}
// Clean up text using regex
{{ nodes.dataFetcher.output.data.description.replace(/\s+/g, ' ').trim() }}
// Build API URL based on environment
{{ `${vars.environment === "production" ? "https://api.prod.com" : "https://api.dev.com"}/v1/users` }}
// Create request headers
{{ { "Authorization": `Bearer ${secrets.API_TOKEN}`, "Content-Type": "application/json" } }}
// Handle different trigger types
{{ trigger.type === "manual" ? `Manual run by ${trigger.triggeredByUser}` : `Automated ${trigger.type} trigger` }}
// Extract branch name from webhook ref
{{ trigger.payload.ref?.match(/refs\/heads\/(.+)/)?.[1] ?? "main" }}
// Safe access to potentially failed nodes
{{ nodes.dataFetcher?.output?.data ?? [] }}
// Check if workflow can proceed (in `if` node, e.g.)
{{ nodes.dataFetcher?.status === "succeeded" && nodes.configLoader?.status !== "failed" }}
// Get current timestamp
{{ new Date().toISOString() }}
// Array methods
{{ nodes.dataFetcher.output.items.map(item => item.name).join(", ") }}
{{ nodes.dataFetcher.output.items.find(item => item.id === vars.targetId) }}
{{ nodes.dataFetcher.output.items.filter(item => item.status === "active").length }}
// Object operations
{{ { id: nodes.userProfile.output.data.id, fullName: `${nodes.userProfile.output.data.firstName} ${nodes.userProfile.output.data.lastName}`, isActive: nodes.userProfile.output.data.status === "active" } }}
{{ { ...vars.defaultConfig, ...nodes.configLoader.output.config } }}
// Conditional operators
{{ vars.environment === "production" ? "prod-api.com" : "dev-api.com" }}
{{ trigger.payload?.user?.email ?? vars.defaultEmail ?? "[email protected]" }}
// JSON operations
{{ JSON.parse(nodes.dataFetcher.output.data.config) }}
{{ JSON.stringify({ id: nodes.userProfile.output.data.id, name: nodes.userProfile.output.data.name }) }}
Limitations
- Expressions are code that must evaluate to a value. Javascript statements are not expressions. That means function, class, or variable declarations are not allowed.
- Each expression must be on a single line. Multiline expressions are just treated as normal strings.
- No access to external APIs or network calls apart from the available global objects.
- Limited to synchronous operations.
Last updated on