Skip to main content

Workflow Variables

Workflow variables store and pass data between tasks. They can be defined in the variables section of a workflow or task, and assigned in inputs, variables, and outputs sections.

Types of Variables​

System Variables​

Automatically created and accessed using double curly braces:

  • {{ workflowId }}: ID of the workflow
  • {{ workflowName }}: Name of the workflow
  • {{ organizationId }}: ID of the organization
  • {{ currentUserId }}: ID of the current user
  • {{ currentEmployeeId }}: ID of the current employee

User-Defined Variables​

Created and used by workflow designers.

Accessing Variables​

Basic Syntax​

Use double curly braces: {{ variableName }}

Nested Variables​

Use dot notation: {{ order.orderId }}

Optional Chaining​

Use ?. to avoid errors with undefined properties:

inputs:
to: "{{order?.customer?.email?}}"

Working with List Variables​

List variables can be accessed using square brackets.

For example, to access the first item in the commodities array variable, use {{ commodities[0] }}.

Accessing List Item​

List variables can be accessed using square brackets.

For example, to access the first item in the commodities array variable, use {{ commodities[0] }}.

Filtering List​

Nested list can be filtered using square brackets. For example, to filter commodities by type, use {{ commodities[type="box"] }}.

This will return all commodities with a type of box.

Accessing Item in Filtered List​

Nested arrays can be accessed using square brackets. For example, to access the first item in the commodities array variable, use {{ commodities[type="box"].[0] }}.

This will return the first commodity with a type of box.

Flatten List​

To flat the list of orders in the jobs variable, use {{ jobs[*].orders }}.

This will return a list of orders.

Recursive Flatten​

To recursively flatten a self-referencing structure (like nested commodities), use [**].

For example, if commodities can contain other commodities:

inputs:
allCommodities: "{{ order.commodities[**] }}"

This will return all commodities at all nesting levels in a single flat list.

Example structure:

Box A (level 1)
β”œβ”€β”€ Package 1 (level 2)
β”‚ β”œβ”€β”€ Item X (level 3)
β”‚ └── Item Y (level 3)
└── Package 2 (level 2)
└── Item Z (level 3)

Using containerCommodities[**] returns: [Box A, Package 1, Item X, Item Y, Package 2, Item Z]

Depth Index​

Use [-N] to filter items by depth from the end of a self-referencing structure:

  • [-1] - Returns leaf nodes (items with no children)
  • [-2] - Returns parents of leaf nodes (second-to-last level)
  • [-3] - Returns grandparents of leaf nodes (third-to-last level)

Examples:

inputs:
# Get all leaf items (deepest level)
leafItems: "{{ order.commodities[**][-1] }}"

# Get all packages (parents of leaf items)
packages: "{{ order.commodities[**][-2] }}"

# Get all boxes (grandparents of leaf items)
boxes: "{{ order.commodities[**][-3] }}"

Example with variable depths:

If you have commodities with varying nesting depths (some 3 levels, some 4 levels), [-2] will always return the parents of the deepest items in each branch:

Box A β†’ Package 1 β†’ Item X (3 levels)
Box B β†’ Package 2 β†’ Subpackage β†’ Item Y (4 levels)

Using [**][-2] returns: [Package 1, Subpackage] (parents of leaves in each branch)

inputs:
leafItems: "{{ order.commodities.containerCommodities[**][-1] }}"
packages: "{{ order.commodities.containerCommodities[**][-2] }}"
boxes: "{{ order.commodities.containerCommodities[**][-3] }}"

Field Selection​

To select a specific field from a list of orders, use {{ jobs[*].orders.[*].{ id customerId parentJobId:_.jobId } }}.

  • To access parent object fields, use _.fieldName.
  • To use aliases, use alias:fieldName.

This will return a list of orders with the following fields: id, customerId and parentJobId.

[
{
"id": "1",
"customerId": "1",
"parentJobId": "1"
},
{
"id": "2",
"customerId": "2",
"parentJobId": "1"
}
]

Sub Expressions​

Sub Expressions can be used to access nested variables. Subexpressions are surrounded by parentheses. For example {{order.items.[status=(selected_status)].[0].name}}.

Sub Expressions are useful when you need to map specific reference data based on the value of another variable.

Sub Expression Example​

The following example shows how to map a state code based on the state name.

inputs:
address:
stateCode: "{{states.[name=(address.stateName)].code}}"

Converting Variables​

Variables can be converted to a specific type using the following functions:

  • string - converts a variable to a string
  • int - converts a string to an integer
  • decimal - converts a string to a decimal
  • bool - converts a string to a boolean
  • datetime - converts a string to a datetime
  • luceneString - converts a string to a lucene string
  • emptyIfNull - returns empty string if the variable is null
  • nullIfEmpty - returns null if the variable is empty
  • transliterate - converts a cyrillic string to a latin string
  • parseJson - parses a json string
  • toJson - converts a variable to a json string
inputs:
orderId: "{{ string orderId }}"

Transliterate​

Transliterate is used to convert a cyrillic string to a latin string.

Example of Transliterate:

inputs:
name: "{{ transliterate name }}" # transliterate name ΠŸΡ€ΠΈΠ²Ρ–Ρ‚, як справи?"; will be converted to "Privit, yak spravi?"

toJson​

toJson is used to convert a variable to a json string.

Example of toJson:

inputs:
orderJson: "{{ toJson order }}"

parseJson​

parseJson is used to parse a json string.

Example of parseJson:

inputs:
order: "{{ parseJson orderJson }}"

Workflow-Level Variable Sources​

Workflow-level variables (defined in the top-level variables section) support three resolution strategies:

Static Value / Template​

The default. Assigns a static value or template expression:

variables:
- name: "organizationId"
value: "{{ Order.OrganizationId }}"
- name: "threshold"
value: 100

From Configuration​

Loads the value from an organization configuration record:

variables:
- name: "apiKey"
fromConfig:
configName: "apps.novaposhta"
key: "apiKey"
- name: "allSettings"
fromConfig:
configName: "apps.novaposhta"
# Omit key to get the entire config object

NCalc Expression​

Evaluates an NCalc expression at runtime. The expression has access to all previously resolved variables:

variables:
- name: "baseRate"
value: 10
- name: "adjustedRate"
expression: "[baseRate] * 1.15"
- name: "isHighValue"
expression: "[orderTotal] > 5000"
tip

Variables are resolved in order, so a variable using expression can reference variables defined earlier in the list.

Complex Variables​

Extends​

Extends are used to extend the inputs of an activity. The inputs of an activity can be extended using the following syntax:

inputs:
order:
extends: "purchaseOrder"
defaultIfNull: []
mapping: # override or extend the order input
customerId: "{{ customerId }}"

Expression​

Initializes a variable using an expression.

inputs:
orderDate:
expression: "now()"

Foreach collection item​

Foreach collection item is used to iterate over a collection of items and construct a new collection of items.

Example of ForEach variables section:

activities:
- name: "UpdateCommodityStatus"
description: "Update the status of a commodity for shipped orders."
steps:
- task: "UpdateCommodityStatus"
inputs:
commodities:
foreach: "order.commodities"
item: "item"
mapping: # object mapping
commodityId: "{{item.id}}"
status: "Shipped"
commoditiesIds:
foreach: "order.commodities"
item: "item"
mapping: "{{item.id}}" # simple array mapping

Switch Cases​

Switch cases are used to define a mapping between a value and a result.

Example of Switch Cases:

inputs:
status:
switch: "{{ statusName }}"
cases:
"Shipped": 1
"Delivered": 2
"Returned": 3
default: 0

Coalesce​

Coalesce is used to return the first non-null value.

Example of Coalesce:

inputs:
status:
coalesce:
- "{{ order.status }}"
- "{{ order.statusName }}"
- "Pending"

Encrypt and Decrypt​

Encrypt and Decrypt are used to encrypt and decrypt sensitive data.

Example of Encrypt and Decrypt:

variables:
creditCard:
encrypt:
value: "{{ creditCard }}"
keyName: "stripe" # key name is used to lookup the encryption key
inputs:
creditCard:
number:
decrypt:
encryptedValue: "{{ creditCard.number }}"
keyName: "stripe" # key name is used to lookup the encryption key

Resolve​

Resolve performs an entity lookup by querying a GraphQL collection and returning a specific field (typically the entity ID). This is useful when you have a human-readable identifier (like a name or code) and need to resolve it to a database ID.

The resolve directive uses a pre-processor that batches and caches all resolve requests before the workflow step executes, preventing N+1 query issues.

inputs:
customerId:
resolve:
entity: "Contact" # Entity type to query (pluralized automatically: contacts)
filter: "name={{ customerName }}" # Lucene filter expression
field: "contactId" # Field to return (default: <entity>Id, e.g., contactId)

Properties:

PropertyTypeRequiredDescription
entitystringYesEntity type name (e.g., Contact, PackageType). Automatically pluralized for the GraphQL query.
filterstringYesLucene filter expression to find the entity. Supports template variables.
fieldstringNoField to return from the matched entity. Defaults to <entity>Id (e.g., contactId for Contact).

Example β€” Resolving a package type by name:

inputs:
commodities:
foreach: "importedCommodities"
item: "item"
mapping:
packageTypeId:
resolve:
entity: "PackageType"
filter: "name={{ item.packageTypeName }}"
weight: "{{ item.weight }}"
description: "{{ item.description }}"
info

Resolve results are cached per unique combination of entity + filter + field. When multiple items in a foreach reference the same entity (e.g., the same package type name), only one GraphQL query is executed.

$raw and $eval​

$raw and $eval are used to define raw and evaluated expressions.

Example of $raw and $eval:

inputs:
order:
$raw: |
{
"id": 1,
"customerId": 1
}
order:
$eval: |
{
"id": "{{ orderId }}",
"customerId": "{{ customerId }}"
}

Raw will use string as is and eval will parse the string as JSON object.