The Falcon engine uses the @stackone/expressions library to evaluate dynamic values throughout connector YAML files. Three expression formats are available, each suited to different use cases.
Quick rule of thumb:
- JSONPath (
$.path) — Direct value access
- String Interpolation (
${...}) — Building strings/URLs
- JEXL (
'{{...}}') — Logic, conditions, and transformations
JSONPath Expressions
When an expression starts with $, it is treated as a JSONPath expression. Use this for direct data access without transformation.
Syntax
| Operator | Description | Example |
|---|
$ | Root object | $.user.name |
. | Child operator | $.user.name |
@ | Current object (in filters) | @.age |
* | Wildcard — all elements/properties | $.users[*] |
.. | Recursive descent | $..name |
[] | Subscript | $.users[0] |
[,] | Union | $.users[0,2] |
[start:end:step] | Array slice | $.users[0:2] |
[?(expression)] | Filter expression | $.users[?(@.age > 30)] |
() | Script expression | Custom expressions |
Available Contexts
| Context | Description | Example |
|---|
$.credentials | Authentication credentials | $.credentials.apiKey |
$.inputs | Action input parameters | $.inputs.userId |
$.config | Connector configuration | $.config.region |
$.steps.{stepId}.output | Previous step output | $.steps.fetch_users.output.data |
$.response | Current response (in response handling) | $.response.pagination.cursor |
$.iterator | Current foreach iteration context | $.iterator.item |
Examples
# Simple value access
token: $.credentials.apiKey
value: $.inputs.employee_id
# Step output
dataSource: $.steps.fetch_users.output.data
# Nested access
expression: $.user.address.city
# Bracket notation (for keys with special characters)
value: $["info/email"]
# Array access
value: $.steps.get_all_employees.output.data.employees[0].id
# Wildcard
iterator: $.steps.get_all_employees.output.data.employees[*].id
For more details on JSONPath syntax, refer to the JSONPath specification.
String Interpolation
Use ${...} syntax to embed values within strings. This is the preferred method for building URLs, composing strings, and simple variable substitution.
Syntax
The content inside ${...} supports the same dot-notation paths as JSONPath but without the $ root prefix.
Examples
# URL path parameters
url: /users/${inputs.id}
url: /users/${inputs.userId}/posts/${inputs.postId}
# Dynamic base URLs
baseUrl: https://${credentials.subdomain}.api.provider.com
baseUrl: https://api.${credentials.region}.provider.com/v2
# OAuth callback URLs
redirect_uri: ${apiHostUri}/connect/oauth2/${connector}/callback
# Composing strings
value: Bearer ${credentials.accessToken}
String interpolation only substitutes values — it does not support operators, conditionals, or function calls. Use JEXL expressions for logic.
JEXL Expressions
JEXL (JavaScript Expression Language) expressions are enclosed in double curly brackets {{expression}} and support variables, operators, functions, and conditional logic. In YAML, these must be wrapped in single quotes: '{{...}}'.
Operators
Arithmetic
| Operator | Description | Example | Result |
|---|
+ | Addition / string concatenation | {{x + y}} | 15 |
- | Subtraction | {{x - y}} | 5 |
* | Multiplication | {{x * 2}} | 20 |
/ | Division | {{x / y}} | 2 |
// | Floor division | {{7 // 2}} | 3 |
% | Modulus | {{x % 3}} | 1 |
^ | Exponentiation | {{2 ^ 3}} | 8 |
Comparison
| Operator | Description | Example | Result |
|---|
== | Equal | {{x == 10}} | true |
!= | Not equal | {{x != y}} | true |
> | Greater than | {{x > y}} | true |
>= | Greater than or equal | {{x >= 10}} | true |
< | Less than | {{x < 100}} | true |
<= | Less than or equal | {{x <= 10}} | true |
in | Element of string or array | {{x in [1, 2, 3]}} | false |
Logical
| Operator | Description | Example |
|---|
! | Logical NOT | {{!active}} |
&& | Logical AND | {{x > 5 && y < 10}} |
|| | Logical OR | {{x < 5 || y > 10}} |
Special
| Operator | Description | Example |
|---|
? : | Ternary operator | {{x > 10 ? "big" : "small"}} |
?? | Nullish coalescing | {{x ?? "default"}} |
Identifiers and Context Access
Access variables from the evaluation context using dot notation:
# Given context: { name: { first: "John" }, jobs: ["Developer", "Designer"] }
'{{name.first}}' # "John"
'{{jobs[1]}}' # "Designer"
Collection Filtering
Filter arrays using bracket expressions:
# Given context: { users: [{ name: "John", age: 30 }, { name: "Jane", age: 25 }] }
'{{users[.name == "John"].age}}' # 30
'{{users[.age > 25].name}}' # ["John"]
Common Patterns in Connector YAML
# Conditional argument inclusion
condition: '{{present(inputs.filter)}}'
# Default values
value: '{{inputs.limit || 100}}'
value: '{{$.inputs.status ?? "active"}}'
# Ternary logic
value: '{{$.count > 0 ? $.count : 0}}'
# String concatenation
expression: '{{$.first_name + " " + $.last_name}}'
# Null checking with fallback
value: '{{present(inputs.cursor) ? inputs.cursor : null}}'
# Complex conditionals
condition: '{{present(inputs.start_date) && present(inputs.end_date)}}'
Built-in Functions
The expression engine includes a set of built-in functions available in JEXL expressions.
Presence & Type Checking
present(value)
Returns true if the value is not null or undefined. This is the most commonly used function in connector YAML for conditional argument inclusion.
# Include query param only if provided
condition: '{{present(inputs.filter)}}'
condition: '{{present(inputs.cursor)}}'
# Check step output exists
condition: '{{present(steps.fetch_data.output.data)}}'
| Input | Result |
|---|
present("hello") | true |
present(0) | true |
present([]) | true |
present({}) | true |
present(null) | false |
present(undefined) | false |
missing(value)
Returns true if the value is null or undefined. The inverse of present().
condition: '{{missing(inputs.optional_field)}}'
String Functions
capitalize(value, mode?)
Capitalizes characters in a string. By default capitalizes the first character; use 'each' mode to capitalize each word.
value: '{{capitalize(inputs.name)}}' # "john" → "John"
value: '{{capitalize(inputs.name, "each")}}' # "john doe" → "John Doe"
truncate(value, maxLength, suffix?)
Truncates a string to a maximum length, appending a suffix (default "...").
value: '{{truncate(inputs.description, 100)}}' # Truncate to 100 chars with "..."
value: '{{truncate(inputs.title, 50, "…")}}' # Custom suffix
value: '{{truncate(inputs.note, 80, "")}}' # No suffix
padStart(value, targetLength, padString?)
Pads the start of a string to reach a target length. Numbers are converted to strings automatically.
value: '{{padStart(inputs.id, 8, "0")}}' # "42" → "00000042"
value: '{{padStart(5, 3, "0")}}' # 5 → "005"
encodeBase64(value)
Encodes a string to Base64.
value: '{{encodeBase64(credentials.apiKey + ":" + credentials.secret)}}'
decodeBase64(value)
Decodes a Base64 string.
value: '{{decodeBase64(inputs.encoded_data)}}'
regexMatch(value, pattern, groupIndex?)
Extracts a value from a string using a regular expression. Returns the specified capture group (default: 1) or null if no match.
# Extract cursor from Link header
value: '{{regexMatch(steps.request.output.headers.link, "after=([^&>]+)", 1)}}'
# Extract numeric ID
value: '{{regexMatch(inputs.reference, "user_id=(\\d+)", 1)}}'
# Full match (group 0)
value: '{{regexMatch(inputs.text, "World", 0)}}'
Similar to regexMatch but simplified for common extraction patterns.
value: '{{extractMatch(inputs.url, "id=(\\w+)")}}'
Array Functions
includes(array, value)
Checks if an array includes a value. If value is an array, checks that ALL values are included.
condition: '{{includes(inputs.roles, "admin")}}'
condition: '{{includes(inputs.scopes, ["read", "write"])}}' # All must match
includesSome(array, value)
Checks if an array includes AT LEAST ONE of the given values.
condition: '{{includesSome(inputs.tags, ["urgent", "critical"])}}'
dedupe(array)
Removes duplicate entries from an array, preserving order. Works with primitives and objects (compared by content with sorted keys).
value: '{{dedupe(steps.merge_data.output.data.ids)}}'
| Input | Result |
|---|
dedupe([1, 2, 2, 3, 1]) | [1, 2, 3] |
dedupe(["a", "b", "a"]) | ["a", "b"] |
dedupe([{id: 1}, {id: 2}, {id: 1}]) | [{id: 1}, {id: 2}] |
join(array, separator?)
Joins array elements into a string with a separator (default: ","). Filters out null/undefined values.
value: '{{join(inputs.fields, ",")}}'
value: '{{join(inputs.tags, " - ")}}'
value: '{{join(["first", "last"], " ")}}'
| Input | Result |
|---|
join(["a", "b", "c"]) | "a,b,c" |
join(["a", "b"], " - ") | "a - b" |
join(["a", null, "b"]) | "a,b" |
join(["a", "b", "c"], "") | "abc" |
reduce(array, operation, field?)
Applies an aggregate operation to an array. When field is specified, that field is extracted from each object before applying the operation.
Supported operations:
| Operation | Description |
|---|
"sum" | Adds all numeric values |
"avg" | Averages all numeric values |
"count" | Returns the number of items |
"min" | Returns the smallest numeric value |
"max" | Returns the largest numeric value |
"concat" | Extracts an array field from each object (requires field) and concatenates the results into a single flat array |
"flatten" | Flattens one level of nesting when the array elements are themselves arrays (no field needed) |
# Sum an array of numbers
value: '{{reduce([1, 2, 3], "sum")}}' # 6
# Sum a field across objects
value: '{{reduce(steps.fetch.output.data.items, "sum", "price")}}'
# Average a field across objects
value: '{{reduce(steps.fetch.output.data.scores, "avg", "value")}}' # 85
# Count items
value: '{{reduce(steps.fetch.output.data.records, "count")}}'
# Concatenate nested arrays from a field
value: '{{reduce(steps.fetch.output.data.items, "concat", "tags")}}' # ["a", "b", "c"]
# Flatten an array of arrays (no field required)
value: '{{reduce([[1, 2], [3, 4]], "flatten")}}' # [1, 2, 3, 4]
| Input | Result |
|---|
reduce([1, 2, 3], "sum") | 6 |
reduce([{v: 10}, {v: 20}], "sum", "v") | 30 |
reduce([{v: 80}, {v: 90}], "avg", "v") | 85 |
reduce([1, 2, 3], "count") | 3 |
reduce([{v: 1}, {v: 3}], "max", "v") | 3 |
reduce([{t: ["a"]}, {t: ["b"]}], "concat", "t") | ["a", "b"] |
reduce([[1, 2], [3, 4]], "flatten") | [1, 2, 3, 4] |
reduce(null, "sum") | null |
Object Functions
keys(object)
Returns the keys of an object as an array. Returns empty array for non-objects.
value: '{{keys(steps.fetch_data.output.data.metadata)}}'
values(object)
Returns the values of an object as an array. Returns empty array for non-objects.
value: '{{values(steps.fetch_data.output.data.attributes)}}'
zipObject(keys, values)
Combines two parallel arrays into an object by pairing keys[i] with values[i]. If the arrays are different lengths, extra elements from the longer array are ignored. Non-string keys are skipped. Returns {} if either input is missing or not an array.
# Build an object from field names and values
value: '{{zipObject(steps.fetch.output.data.fields, steps.fetch.output.data.values)}}'
# Combine static keys with dynamic values
value: '{{zipObject(["name", "email"], [inputs.name, inputs.email])}}'
| Input | Result |
|---|
zipObject(["a", "b"], [1, 2]) | {a: 1, b: 2} |
zipObject(["name", "email"], ["John", "john@example.com"]) | {name: "John", email: "john@example.com"} |
zipObject(["a", "b", "c"], [1, 2]) | {a: 1, b: 2} |
zipObject([], []) | {} |
zipObject(null, [1, 2]) | {} |
groupBy(array, key)
Array utility that groups items but returns an object result; it’s documented here alongside object helpers for convenience (see the Array Functions section for related utilities). Groups an array of objects by the value of a specified key, producing an object where each unique key value maps to an array of matching items. Items without the key or with a null/undefined value for that key are collected under "__missing__". Returns {} if the input is missing, not an array, or the key is not a string.
# Group employees by department
value: '{{groupBy(steps.fetch.output.data.employees, "department")}}'
# Group by status
value: '{{groupBy(steps.fetch.output.data.records, "status")}}'
| Input | Result |
|---|
groupBy([{dept: "eng", name: "Alice"}, {dept: "hr", name: "Bob"}, {dept: "eng", name: "Carol"}], "dept") | {eng: [{...}, {...}], hr: [{...}]} |
groupBy([{status: "active"}, {status: "inactive"}, {status: "active"}], "status") | {active: [{...}, {...}], inactive: [{...}]} |
groupBy([{name: "Alice"}, {status: "active"}], "status") | {__missing__: [{name: "Alice"}], active: [{status: "active"}]} |
groupBy(null, "key") | {} |
Date Functions
now()
Returns the current date/time in ISO 8601 format.
value: '{{now()}}' # "2025-04-10T12:00:00.000Z"
Calculates the number of complete years between two dates. If endDate is omitted, uses today.
value: '{{yearsElapsed(inputs.hire_date, "yyyy-MM-dd")}}'
value: '{{yearsElapsed("2015-01-01", "yyyy-MM-dd", "2025-01-01")}}' # 10
Checks whether a date (optionally with years added) has already passed.
condition: '{{hasPassed(inputs.expiry_date, "yyyy-MM-dd")}}'
condition: '{{hasPassed(inputs.start_date, "yyyy-MM-dd", 5)}}' # Has start + 5 years passed?
Calculates the next anniversary of a date.
value: '{{nextAnniversary(inputs.hire_date, "dd/MM/yyyy")}}'
Calculates milliseconds between a date and now. Supports ISO strings (default), Unix timestamps ('timestamp'), and seconds ('seconds').
value: '{{deltaFromNowMs(inputs.expires_at)}}'
value: '{{deltaFromNowMs(inputs.timestamp, "timestamp")}}'
Expression Selection Guide
Choosing the right expression format depends on the context:
| Scenario | Format | Example |
|---|
| Access a credential value | JSONPath | $.credentials.apiKey |
| Access an input parameter | JSONPath | $.inputs.userId |
| Reference step output | JSONPath | $.steps.fetch.output.data |
| Build a URL with parameters | String Interpolation | /users/${inputs.id} |
| Dynamic base URL | String Interpolation | https://${credentials.subdomain}.api.com |
| Provide a default value | JEXL | '{{inputs.limit ?? 100}}' |
| Conditional argument | JEXL | '{{present(inputs.filter)}}' |
| Ternary logic | JEXL | '{{x > 0 ? x : 0}}' |
| String concatenation with logic | JEXL | '{{inputs.first + " " + inputs.last}}' |
| Enum matching | JEXL | '{{$.status == "ACTIVE"}}' |
Do not mix expression formats within a single value. Use one format per field:
- Correct:
value: '{{$.inputs.limit ?? 25}}'
- Incorrect:
value: '$.inputs.limit ?? 25'
Validation and Error Handling
isValidExpression(expression)
The expression engine validates syntax before evaluation. Invalid expressions return errors that help identify issues.
Incremental JSONPath Errors
When using the expression engine with incrementalJsonPath: true, detailed error messages are provided for JSON Path evaluation failures:
Key 'age' not found at '$.user.details'. Available keys: name
This is used internally by the AI Builder for self-repair during connector development.
Safe Evaluation
The safeEvaluate function returns null instead of throwing errors, which is useful for optional expressions and fallback patterns.