> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Expression Language

> Complete reference for JSONPath, JEXL, and string interpolation expressions in connector YAML

The Falcon engine uses the [`@stackone/expressions`](https://www.npmjs.com/package/@stackone/expressions) library to evaluate dynamic values throughout connector YAML files. Three expression formats are available, each suited to different use cases.

<Tip>
  **Quick rule of thumb:**

  * **JSONPath** (`$.path`) — Direct value access
  * **String Interpolation** (`${...}`) — Building strings/URLs
  * **JEXL** (`'{{...}}'`) — Logic, conditions, and transformations
</Tip>

***

## 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

```yaml theme={null}
# 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](https://goessner.net/articles/JsonPath/).

***

## String Interpolation

Use `${...}` syntax to embed values within strings. This is the preferred method for building URLs, composing strings, and simple variable substitution.

### Syntax

```
${variable.path}
```

The content inside `${...}` supports the same dot-notation paths as JSONPath but without the `$` root prefix.

### Examples

```yaml theme={null}
# 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}
```

<Warning>
  String interpolation only substitutes values — it does not support operators, conditionals, or function calls. Use JEXL expressions for logic.
</Warning>

***

## 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:

```yaml theme={null}
# Given context: { name: { first: "John" }, jobs: ["Developer", "Designer"] }
'{{name.first}}'    # "John"
'{{jobs[1]}}'        # "Designer"
```

### Collection Filtering

Filter arrays using bracket expressions:

```yaml theme={null}
# 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

```yaml theme={null}
# 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.

```yaml theme={null}
# 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()`.

```yaml theme={null}
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.

```yaml theme={null}
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 `"..."`).

```yaml theme={null}
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.

```yaml theme={null}
value: '{{padStart(inputs.id, 8, "0")}}'    # "42" → "00000042"
value: '{{padStart(5, 3, "0")}}'             # 5 → "005"
```

#### `encodeBase64(value)`

Encodes a string to Base64.

```yaml theme={null}
value: '{{encodeBase64(credentials.apiKey + ":" + credentials.secret)}}'
```

#### `decodeBase64(value)`

Decodes a Base64 string.

```yaml theme={null}
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.

```yaml theme={null}
# 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)}}'
```

#### `extractMatch(value, pattern)`

Similar to `regexMatch` but simplified for common extraction patterns.

```yaml theme={null}
value: '{{extractMatch(inputs.url, "id=(\\w+)")}}'
```

### Crypto Functions

#### `sha256(value, encoding?)`

Computes the SHA-256 hash of a string. Useful for request signing and content verification.

* `value` (string): The string to hash
* `encoding` (string, optional): `'hex'` (default) or `'base64'`
* Returns: hash string, or empty string for invalid input

```yaml theme={null}
value: '{{sha256(inputs.body)}}'
value: '{{sha256(inputs.body, "base64")}}'
```

| Input                       | Result                                |
| --------------------------- | ------------------------------------- |
| `sha256("hello")`           | `"2cf24dba5fb0a30e..."` (64-char hex) |
| `sha256("hello", "base64")` | `"LPJNul+wow4m6Dsq..."` (base64)      |
| `sha256(null)`              | `""`                                  |

#### `hmacSha256(value, key, encoding?)`

Computes an HMAC-SHA256 signature using a secret key. Used for webhook signature verification and API request signing.

* `value` (string): The string to sign
* `key` (string): The secret key
* `encoding` (string, optional): `'hex'` (default) or `'base64'`
* Returns: HMAC digest, or empty string for invalid input

```yaml theme={null}
value: '{{hmacSha256(inputs.body, credentials.signingSecret)}}'
value: '{{hmacSha256(inputs.body, credentials.signingSecret, "base64")}}'
```

| Input                                     | Result                                |
| ----------------------------------------- | ------------------------------------- |
| `hmacSha256("hello", "secret")`           | `"88aab3ede8d3adf9..."` (64-char hex) |
| `hmacSha256("hello", "secret", "base64")` | base64-encoded HMAC                   |
| `hmacSha256(null, "secret")`              | `""`                                  |
| `hmacSha256("hello", null)`               | `""`                                  |

#### `md5(value, encoding?)`

Computes the MD5 hash of a string.

* `value` (string): The string to hash
* `encoding` (string, optional): `'hex'` (default) or `'base64'`
* Returns: hash string, or empty string for invalid input

```yaml theme={null}
value: '{{md5(inputs.body)}}'
value: '{{md5(inputs.body, "base64")}}'
```

| Input                    | Result                               |
| ------------------------ | ------------------------------------ |
| `md5("hello")`           | `"5d41402abc4b2a76b9719d911017c592"` |
| `md5("hello", "base64")` | `"XUFAKrxLKna5cZ2REBfFkg=="`         |
| `md5(null)`              | `""`                                 |

***

### Array Functions

#### `includes(array, value)`

Checks if an array includes a value. If `value` is an array, checks that ALL values are included.

```yaml theme={null}
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.

```yaml theme={null}
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).

```yaml theme={null}
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.

```yaml theme={null}
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)                   |

```yaml theme={null}
# 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.

```yaml theme={null}
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.

```yaml theme={null}
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.

```yaml theme={null}
# 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.

```yaml theme={null}
# 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.

```yaml theme={null}
value: '{{now()}}'   # "2025-04-10T12:00:00.000Z"
```

#### `yearsElapsed(startDate, format, endDate?)`

Calculates the number of complete years between two dates. If `endDate` is omitted, uses today.

```yaml theme={null}
value: '{{yearsElapsed(inputs.hire_date, "yyyy-MM-dd")}}'
value: '{{yearsElapsed("2015-01-01", "yyyy-MM-dd", "2025-01-01")}}'   # 10
```

#### `hasPassed(date, format, yearsToAdd?)`

Checks whether a date (optionally with years added) has already passed.

```yaml theme={null}
condition: '{{hasPassed(inputs.expiry_date, "yyyy-MM-dd")}}'
condition: '{{hasPassed(inputs.start_date, "yyyy-MM-dd", 5)}}'   # Has start + 5 years passed?
```

#### `nextAnniversary(initialDate, format)`

Calculates the next anniversary of a date.

```yaml theme={null}
value: '{{nextAnniversary(inputs.hire_date, "dd/MM/yyyy")}}'
```

#### `deltaFromNowMs(dateInput, format?)`

Calculates milliseconds between a date and now. Supports ISO strings (default), Unix timestamps (`'timestamp'`), and seconds (`'seconds'`).

```yaml theme={null}
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"}}'`               |

<Warning>
  **Do not mix expression formats within a single value.** Use one format per field:

  * **Correct:** `value: '{{$.inputs.limit ?? 25}}'`
  * **Incorrect:** `value: '$.inputs.limit ?? 25'`
</Warning>

***

## 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.
