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

# JSONata cookbook

> Common patterns by example.

JSONata is a small query language for JSON. This page is a list of patterns you'll reach for often.

For the full spec, see [jsonata.org](https://jsonata.org). The patterns below are the ones that cover \~90% of workflow use.

## Reference fields

| Want                   | JSONata                            |
| ---------------------- | ---------------------------------- |
| Top-level input field  | `{{ email }}`                      |
| Nested input field     | `{{ contact.email }}`              |
| Step output field      | ``{{ `Lookup contact`.body.id }}`` |
| Top-level by full path | `{{ ctx.input.email }}`            |

Backticks are required when a step name contains spaces or special characters.

## Comparisons and logic

```jsonc theme={null}
{{ score > 50 }}
{{ tier = 'gold' }}              // single =, not ==
{{ tier in ['gold', 'platinum'] }}
{{ score > 50 and tier = 'gold' }}
{{ found = false or status_code != 200 }}
{{ not (tier = 'free') }}
```

## Defaults and null-checks

```jsonc theme={null}
{{ name ? name : 'friend' }}                     // ternary
{{ $exists(name) ? name : 'unknown' }}           // explicit existence
{{ contact.email ?? 'no-reply@example.com' }}    // null-coalesce
```

## String operations

```jsonc theme={null}
{{ first_name & ' ' & last_name }}               // concat
{{ $uppercase(name) }}
{{ $lowercase(email) }}
{{ $trim(' hello ') }}
{{ $contains(message, 'urgent') }}
{{ $substring(phone, 0, 3) }}
{{ $replace(phone, '-', '') }}
```

## Number operations

```jsonc theme={null}
{{ price * 1.08 }}                                // tax
{{ $round(price * quantity, 2) }}
{{ $floor(score) }}
{{ $abs(delta) }}
{{ $sum(items.price) }}                           // aggregate
{{ $count(items) }}
{{ $average(scores) }}
```

## Array operations

```jsonc theme={null}
// indexing
{{ contacts[0] }}
{{ contacts[-1] }}                                // last element

// filter
{{ contacts[tier = 'gold'] }}                     // all gold contacts
{{ contacts[score > 50][0] }}                     // first high-scoring

// map (project a field)
{{ contacts.email }}                              // ['a@b', 'c@d']
{{ contacts.{ "id": id, "label": name } }}        // reshape each item

// boolean over array
{{ $count(contacts[tier = 'gold']) > 0 }}         // any gold?
{{ $count(contacts[tier = 'gold']) = $count(contacts) }}  // all gold?

// concat / flatten
{{ list_a & list_b }}                             // arrays merge with &? — use [list_a, list_b].**
{{ $each(contacts, function($v) { $v.email }) }}
```

## Object operations

```jsonc theme={null}
{{ $keys(contact) }}                              // ['id', 'email', ...]
{{ $merge([contact, {"verified": true}]) }}       // shallow merge
{{ contact.{ "name": $.name, "tier": $.tier } }}  // pick fields
```

## Conditionals

```jsonc theme={null}
{{ score > 50 ? 'qualified' : 'not qualified' }}

// chained
{{ score > 90 ? 'a' : (score > 70 ? 'b' : (score > 50 ? 'c' : 'fail')) }}

// switch-style
{{ {
  "high":   score > 80,
  "medium": score > 50 and score <= 80,
  "low":    score <= 50
}[$$ = true] }}                                   // returns the matching key
```

## Date operations

```jsonc theme={null}
{{ $now() }}                                      // current ISO timestamp
{{ $millis() }}                                   // current epoch ms
{{ $fromMillis(ms) }}
{{ $toMillis(iso_string) }}
```

For richer date math, do it in a Code step instead.

## Common workflow snippets

### Build a Slack mention

```jsonc theme={null}
"text": "{{ '<@' & user_id & '> new lead from ' & $.name }}"
```

### Extract domain from email

```jsonc theme={null}
"domain": "{{ $substringAfter(email, '@') }}"
```

### First non-null

```jsonc theme={null}
"phone": "{{ contact.mobile ?? contact.work ?? contact.home }}"
```

### Pick by enum

```jsonc theme={null}
"slack_channel": "{{ {
  'billing': 'C_BILLING',
  'support': 'C_SUPPORT',
  'sales':   'C_SALES'
}[intent] }}"
```

### Sum with filter

```jsonc theme={null}
"qualified_revenue": "{{ $sum(leads[score > 50].arr) }}"
```

## Debugging tips

When an expression isn't producing what you expect:

1. **Inspect `ctx` at the failing step.** Open the run trace, click the step *before* the failing one, and copy the visible `ctx`.
2. **Test the expression manually.** Paste `ctx` and the expression into [try.jsonata.org](https://try.jsonata.org) — same engine.
3. **Add a Code step temporarily** that just returns the raw value, so you can see what JSONata sees.
4. **Check for backticks** around step names with spaces (most common bug).
