# Transformations

Reshape a request body before delivery with a reusable, declarative mapping.

A transformation reshapes the JSON body of an event before it is delivered. Use it to send a service only the fields it needs, rename them, mask sensitive values, or reduce a large vendor payload to a stable shape you control.

Transformations are **declarative**: they are data, not code. There is nothing to deploy or sandbox, and the same mapping runs in the dashboard preview and in the delivery path.

## Anatomy of a transformation

```json
{
  "name": "invoice-paid-summary",
  "keep": false,
  "fields": [
    { "key": "customer",  "value": "data.object.customer" },
    { "key": "amount",    "value": "data.object.amount_paid | divide: 100" },
    { "key": "currency",  "value": "data.object.currency | upper" },
    { "key": "email",     "value": "data.object.customer_email | lower | mask: 6" },
    { "key": "summary",   "value": "\"Invoice {{data.object.number}} paid\"" },
    { "key": "event.id",  "value": "$event.id" }
  ],
  "remove": []
}
```

- **`keep`**: when `true`, the output starts as a copy of the original body; otherwise it starts empty.
- **`remove`**: paths to delete from the output.
- **`fields`**: values to set, in order. A `key` is a dotted path, so `event.id` produces `{ "event": { "id": … } }`.

## Values

A value is one of:

| Form | Example | Meaning |
|---|---|---|
| Path | `data.items[0].sku` | A path into the body. `[-1]` is the last element; `[*]` collects from every element. |
| Special | `$headers.x-request-id`, `$event.id`, `$event.created_at`, `$source.name`, `$body` | Request headers, event metadata, the source name, or the whole body. |
| Literal | `"paid"`, `42`, `true`, `null` | Used as written. |
| Template | `"Order {{data.id}} for {{data.email}}"` | Expressions inside `{{ }}` in a string. |

A path that isn't present produces `null`.

## Filters inside values

Pipe a value through filters, left to right: `data.email | lower | default: "n/a"`.

| Filter | What it does |
|---|---|
| `lower`, `upper`, `trim` | Change case or trim whitespace. |
| `string`, `number`, `boolean`, `json` | Convert the type. |
| `default: x` | Use `x` when the value is empty or missing. |
| `join: ","`, `split: ","` | Convert between arrays and strings. |
| `replace: "a", "b"` | Replace text. |
| `first`, `last`, `length`, `compact` | Work on arrays and strings. |
| `sum`, `round: n`, `times: n`, `divide: n` | Arithmetic. |
| `mask: n` | Replace all but the last `n` characters with `*`. |
| `date` | Convert seconds, milliseconds or a date string to ISO 8601. |

## Create and use one

1. In the dashboard open **Transforms → New transform**, paste a sample body (or use the source's last request), and build the mapping with a live preview.
2. Attach it to a flow with `transform_id`.

```bash
curl -X POST https://api.getensue.com/v1/transforms \
  -H "authorization: Bearer ens_<key>" -H "content-type: application/json" \
  -d '{ "name": "invoice-paid-summary", "keep": false,
        "fields": [{ "key": "customer", "value": "data.object.customer" }] }'
```

Transformations are reusable. Editing one changes every flow that uses it, from the next attempt. A transformation that a flow still uses cannot be deleted (`409 in_use`).

## Limits and failure

| Limit | Value |
|---|---|
| Fields | 100 |
| Removed paths | 50 |
| Output size | 1 MB |

A body that isn't JSON cannot be transformed. The event fails with a transform error and is not delivered, and the timeline says why. A transform failure says nothing about your service, so it does not count towards the [circuit breaker](/gateway/circuit-breaker/).

> [!TIP]
> Filters run before transformations and see the original body. Use `$body` in a transformation if you want to deliver the original alongside a summary.