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" |
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)}}'
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.