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

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

## Expression Formats

<CardGroup cols={2}>
  <Card title="JSONPath" href="#jsonpath-expressions">
    (`$.path`)

    * Directly accessing values
    * No transformation needed
    * Can use value as returned
  </Card>

  <Card title="String Interpolation" href="#string-interpolation">
    (`${...}`)

    * Building strings/URLs
    * Concatenating with static text
    * Simple variable substitution
  </Card>

  <Card title="JEXL" href="#jexl-expressions">
    (`'{{...}}'`)

    * Setting default values (`??` operator)
    * Conditional Logic
    * Complex transformations
  </Card>
</CardGroup>

<Accordion title="Cheatsheet: Quick use examples">
  | 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"}}'`               |
</Accordion>

## 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`                 |
| `$.context`               | Connection context, such as `environment` and `authenticationType` | `$.context.environment`           |
| `$.steps.{stepId}.output` | Previous step output                                               | `$.steps.fetch_users.output.data` |
| `$.iterator`              | Current foreach iteration context                                  | `$.iterator.item`                 |
| `$.result`                | The action's assembled result                                      | `$.result.id`                     |

**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)}}'
```

### Conditional Arguments

Within a step's `args`, attach a `condition` to include an argument only when the condition evaluates to `true`. The most common use of JEXL in connector YAML is omitting optional query parameters, body fields, or headers when the corresponding input is absent.

```yaml theme={null}
args:
  - name: filter
    value: '{{inputs.filter}}'
    in: query
    condition: '{{present(inputs.filter)}}'

  - name: updated_since
    value: $.inputs.updated_after
    in: query
    condition: '{{present(inputs.updated_after)}}'

  - name: include_inactive
    value: "true"
    in: query
    condition: '{{inputs.include_inactive == true}}'
```

***

## JEXL 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)}}'
```

#### `upper(value)` / `lower(value)`

Uppercase or lowercase a string. Returns an empty string for non-string input.

```yaml theme={null}
value: '{{upper(inputs.code)}}'     # "abc" → "ABC"
value: '{{lower(credentials.subdomain)}}'   # "MyOrg" → "myorg"
```

#### `replace(value, search, replacement, replaceAll?)`

Replaces occurrences of a substring. By default replaces only the first; pass `true` as the fourth argument to replace all.

```yaml theme={null}
value: '{{replace(inputs.path, " ", "_")}}'              # first occurrence
value: '{{replace(inputs.path, "-", "_", true)}}'         # all occurrences
```

#### `split(value, separator?)`

Splits a string into an array (default separator `","`). Returns an empty array for non-string input.

```yaml theme={null}
value: '{{split(inputs.fields)}}'          # "a,b,c" → ["a", "b", "c"]
value: '{{split(inputs.tags, " ")}}'        # "a b c" → ["a", "b", "c"]
```

#### `asString(value)`

Converts any value to its string representation (objects/arrays are JSON-stringified). Returns `""` for null/undefined.

```yaml theme={null}
value: '{{asString(inputs.count)}}'    # 42 → "42"
value: '{{asString(inputs.filter)}}'   # { a: 1 } → '{"a":1}'
```

#### `decodeUri(value)`

Decodes a URI-encoded string (percent-decoding). Useful for pre-encoded values like pagination cursors that would otherwise be double-encoded.

```yaml theme={null}
value: '{{decodeUri(steps.request.output.data.next_cursor)}}'   # "a%3Db" → "a=b"
```

#### `urlParam(url, paramName)`

Extracts a query-parameter value from a URL string. Returns `null` if the param is absent or inputs are invalid.

```yaml theme={null}
# Pull the next page number out of a `next` URL
value: '{{urlParam(steps.request.output.data.paging.next, "page")}}'
```

| Input                                                         | Result |
| ------------------------------------------------------------- | ------ |
| `urlParam("https://api.x.com/items?page=2&limit=10", "page")` | `"2"`  |
| `urlParam("https://api.x.com/items?page=2", "missing")`       | `null` |

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

#### `min(...values)`

Returns the smallest number from the arguments. Accepts either individual values or a single array. Returns `null` when no valid numbers are provided.

```yaml theme={null}
value: '{{min(inputs.page_size, 100)}}'    # cap page size at 100
value: '{{min(steps.fetch.output.data.prices)}}'   # smallest of an array
```

| Input            | Result |
| ---------------- | ------ |
| `min(1, 2, 3)`   | `1`    |
| `min([5, 2, 8])` | `2`    |
| `min(42)`        | `42`   |

### 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")`                                                                                     | `{}`                                                             |

#### `parseJson(value)`

Parses a JSON string into its decoded value (object, array, or primitive). Returns `null` on missing/non-string/empty/invalid input. Useful when a provider delivers a field as a JSON-encoded string (e.g. inside a form-urlencoded webhook body).

```yaml theme={null}
# Decode a JSON-encoded `payload` form field from a webhook body
value: '{{parseJson(inputs.payload)}}'
```

| Input                       | Result          |
| --------------------------- | --------------- |
| `parseJson('{"id":"abc"}')` | `{ id: "abc" }` |
| `parseJson('[1,2,3]')`      | `[1, 2, 3]`     |
| `parseJson("not json")`     | `null`          |

### Date Functions

<Warning>
  Date-time values with no UTC offset are resolved in the host timezone. Prefer offset-bearing input (e.g. `...Z` or `+00:00`) when the exact instant matters.
</Warning>

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

#### `unixToIso(value, unit?)`

Converts a Unix epoch timestamp to an ISO-8601 date-time string. Accepts epoch `'seconds'` (default) or `'ms'`, as a number or numeric string. Returns `null` for non-numeric / out-of-range input. Useful when a provider returns epoch timestamps but the defined schema expects a date-time string.

```yaml theme={null}
value: '{{unixToIso(steps.fetch.output.data.created_at)}}'          # seconds
value: '{{unixToIso(steps.fetch.output.data.created_ms, "ms")}}'    # milliseconds
```

| Input                            | Result                       |
| -------------------------------- | ---------------------------- |
| `unixToIso(1769518356)`          | `"2026-01-27T12:52:36.000Z"` |
| `unixToIso(1769518356722, "ms")` | `"2026-01-27T12:52:36.722Z"` |
| `unixToIso("not-a-number")`      | `null`                       |

#### `isoToUnix(value, unit?)`

The inverse of `unixToIso` — converts a date-time string to a Unix epoch timestamp. Returns epoch `'seconds'` (default, floored) or `'ms'`; `null` for unparsable input.

```yaml theme={null}
value: '{{isoToUnix(inputs.updated_at)}}'          # → epoch seconds
value: '{{isoToUnix(inputs.updated_at, "ms")}}'    # → epoch milliseconds
```

#### `formatDate(value, formatString)`

Formats a date-time value into a string using a token-based format. Output is rendered in UTC, so numeric offset tokens and instant fields are stable across host timezones. Accepts an ISO 8601 string (with or without offset) or one of these fallback formats: `yyyy-MM-dd`, `yyyy/MM/dd`, `dd/MM/yyyy`, `dd-MM-yyyy`, `yyyy-dd-MM`, `yyyy/dd/MM`, `MM/dd/yyyy`, `MM-dd-yyyy`. For epoch numbers, compose with `unixToIso` first.

**Common tokens**

| Token    | Meaning                              | Example   |
| -------- | ------------------------------------ | --------- |
| `yyyy`   | 4-digit calendar year                | `2026`    |
| `MM`     | 2-digit month (01–12)                | `01`      |
| `MMM`    | Abbreviated month name               | `Jan`     |
| `MMMM`   | Full month name                      | `January` |
| `dd`     | 2-digit day of month                 | `27`      |
| `do`     | Day of month, ordinal                | `27th`    |
| `EEE`    | Abbreviated weekday                  | `Tue`     |
| `EEEE`   | Full weekday                         | `Tuesday` |
| `HH`     | Hour (00–23)                         | `12`      |
| `hh`     | Hour (01–12)                         | `12`      |
| `mm`     | Minutes (00–59)                      | `52`      |
| `ss`     | Seconds (00–59)                      | `36`      |
| `SSS`    | Milliseconds                         | `722`     |
| `a`      | AM/PM marker                         | `PM`      |
| `xxx`    | Numeric offset (`+00:00`)            | `+00:00`  |
| `X`      | Short offset (`Z` for UTC)           | `Z`       |
| `'text'` | Literal text (wrap in single quotes) | `at`      |

```yaml theme={null}
# Date only
value: '{{formatDate(inputs.created_at, "yyyy-MM-dd")}}'          # "2026-01-27"

# SQL-style datetime
value: '{{formatDate(inputs.created_at, "yyyy-MM-dd HH:mm:ss")}}' # "2026-01-27 12:52:36"

# HTTP Date header (RFC 1123)
value: '{{formatDate(inputs.created_at, "EEE, dd MMM yyyy HH:mm:ss ''GMT''")}}'

# Format the current instant
value: '{{formatDate(now(), "yyyy-MM-dd")}}'                      # today, e.g. "2026-01-27"

# Compose with unixToIso for epoch input
value: '{{formatDate(unixToIso(steps.fetch.output.data.created_at), "yyyy-MM-dd")}}'
```

| Input                                                        | Result               |
| ------------------------------------------------------------ | -------------------- |
| `formatDate("2026-01-27T12:52:36.722Z", "yyyy-MM-dd")`       | `"2026-01-27"`       |
| `formatDate("2026-01-27T12:52:36.722Z", "HH:mm:ss")`         | `"12:52:36"`         |
| `formatDate("2026-01-27T12:52:36.722Z", "EEE, dd MMM yyyy")` | `"Tue, 27 Jan 2026"` |
| `formatDate("not-a-date", "yyyy-MM-dd")`                     | `null`               |

<Warning>
  **Case matters.** Lowercase `mm` is minutes; uppercase `MM` is month (`yyyy-mm-dd` silently renders `"2026-52-27"`). Uppercase `YYYY`-`MM`-`DD` (the SQL/ISO convention) returns `null`. Use `yyyy-MM-dd` instead. For epoch timestamps, use `isoToUnix(value)` instead of the `t`/`T` tokens.
</Warning>

### Advanced Functions

#### `parseJson`, XML & webhook helpers

A few functions exist for specialized transport/webhook scenarios:

| Function                                                                            | Purpose                                                                                                                                                        |
| ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `wrapXmlArray(value, wrapperElement?, typeAttribute?)`                              | Wraps an array so it serializes correctly for XML APIs (used with SOAP/XML request bodies via `fast-xml-parser`).                                              |
| `webhookEventsUrl(webhooksUrl, orgId, projectSecureId, connectorProfileId, token?)` | Builds the StackOne events webhook URL (with a base64url-encoded routing context) for provisioning native provider webhooks. Used inside `events.setup` steps. |

These appear in generated webhook setup and XML/SOAP connectors.

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