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

# Transformer node

> Reshape, map, combine, and convert data between workflow steps with a no-code pipeline of operations. Full TransformerNodeConfig reference: source, operations, the complete operation catalog, {{node_id.field}} references, inputs/outputs, errors, and credit impact.

export const MediaEmbed = ({id, type = 'screenshot', caption = '', ext, ratio = '16 / 9'}) => {
  const isVideo = type === 'video' || type === 'app_video';
  const resolvedExt = ext || (isVideo ? 'mp4' : type === 'screenshot' ? 'webp' : 'svg');
  const src = 'https://media.modulex.dev/' + id + '.' + resolvedExt;
  const [status, setStatus] = useState('loading');
  const [isDev, setIsDev] = useState(false);
  const [inView, setInView] = useState(false);
  const boxRef = useRef(null);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const h = window.location.hostname;
    setIsDev(h === 'localhost' || h === '127.0.0.1' || h.endsWith('.mintlify.app'));
  }, []);
  useEffect(() => {
    if (inView) return;
    if (typeof IntersectionObserver === 'undefined') {
      setInView(true);
      return;
    }
    const el = boxRef.current;
    if (!el) return;
    const io = new IntersectionObserver(entries => {
      if (entries.some(e => e.isIntersecting)) {
        setInView(true);
        io.disconnect();
      }
    }, {
      rootMargin: '300px'
    });
    io.observe(el);
    return () => io.disconnect();
  }, [inView]);
  if (status === 'missing') {
    if (!isDev) return null;
    return <div style={{
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      gap: '0.4rem',
      padding: '1rem 1.25rem',
      margin: '1.25rem 0',
      width: '100%',
      aspectRatio: ratio,
      boxSizing: 'border-box',
      border: '1px dashed rgba(128,128,128,0.45)',
      borderRadius: '0.75rem',
      background: 'rgba(128,128,128,0.06)',
      color: 'currentColor',
      fontSize: '0.85rem',
      lineHeight: 1.45
    }}>
        <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '0.5rem',
      opacity: 0.75
    }}>
          <span aria-hidden="true">🎬</span>
          <code style={{
      fontSize: '0.75rem'
    }}>{id}</code>
          <span style={{
      fontSize: '0.65rem',
      textTransform: 'uppercase',
      letterSpacing: '0.04em',
      padding: '0.1rem 0.4rem',
      borderRadius: '0.4rem',
      background: 'rgba(128,128,128,0.18)'
    }}>
            {type}
          </span>
        </div>
        <div style={{
      opacity: 0.9
    }}>{caption || 'Media not uploaded yet.'}</div>
        <div style={{
      fontSize: '0.7rem',
      opacity: 0.5
    }}>
          Upload to R2 as <code>{id}.{resolvedExt}</code> — preview only, hidden in production.
        </div>
      </div>;
  }
  const mediaStyle = {
    display: status === 'loaded' ? 'block' : 'none',
    width: '100%',
    height: 'auto',
    borderRadius: '0.75rem'
  };
  const media = isVideo ? <video src={inView ? src : undefined} autoPlay loop muted playsInline preload="metadata" onLoadedData={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} /> : <img src={inView ? src : undefined} alt={caption} onLoad={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} />;
  return <figure style={{
    margin: '1.25rem 0'
  }}>
      <div ref={boxRef} style={status === 'loaded' ? {
    width: '100%'
  } : {
    width: '100%',
    aspectRatio: ratio,
    borderRadius: '0.75rem',
    background: 'rgba(128,128,128,0.06)'
  }}>
        {media}
      </div>
      {status === 'loaded' && caption ? <figcaption style={{
    marginTop: '0.5rem',
    textAlign: 'center',
    fontSize: '0.85rem',
    opacity: 0.7
  }}>
          {caption}
        </figcaption> : null}
    </figure>;
};

The `transformer` node reshapes, maps, combines, and converts data as it moves between steps — without writing code. You point it at a source value with a `{{node_id.field}}` reference, then apply an ordered pipeline of operations (pick fields, map a list, parse JSON, format a date, run arithmetic, and so on). The transformed value is written into run state under the node's own id.

Use a transformer when the shape one node produces does not match the shape the next node expects — for example, picking three fields out of a tool's response object, mapping a list of records down to a list of ids, or merging two earlier outputs into one object before an [LLM node](/workflow-builder/nodes/llm) reads them. For branching or looping over data, use a [conditional node](/workflow-builder/nodes/conditional); for raw HTTP, webhooks, or JSON-schema validation, use a [function node](/workflow-builder/nodes/function).

<MediaEmbed id="MX-MEDIA-3140" type="screenshot" caption={"the Transformer node configuration panel in the workflow builder"} />

## What the node does

When the engine compiles your workflow, the `transformer` node becomes a single async step (see [workflow engine & nodes](/concepts/workflow-engine)). At run time the node:

1. Resolves its `source` reference against the current run [state](/workflow-builder/variables-and-references) to get a starting value.
2. If the source resolves to `null` (the reference is missing or out of range), it logs a warning and writes `null` under the node id — the operations are not run.
3. Otherwise it applies each operation in `operations` in order, feeding the result of one operation as the input to the next (a pipeline).
4. Writes the final value into run state under the node's `id`.

Because operations are applied in sequence, order matters: `parse_json` then `pick` first turns a JSON string into an object and then selects fields from it; the reverse order would fail to find any fields on a string.

<Note>
  The output of a transformer node is always stored under the node's `id` — for example a node with `id: "shape_1"` writes to `{{shape_1}}`. The legacy `output_key` and `source_key` fields are deprecated and should not be set; use `source` with a `{{node_id.field}}` reference instead. See [inputs & outputs](#inputs-and-outputs).
</Note>

## Configuration (`TransformerNodeConfig`)

These fields live on the node's `transformer_config`. In the builder you set them through the detail panel; over the API they appear inside the node definition (the builder also accepts a wrapped `{config: {...}}` form, which the backend normalizes to `transformer_config`).

<ParamField path="source" type="string">
  The value to transform, written as a single `{{node_id.path}}` reference (for example `{{node_fetch.result.items}}`). A reference that is exactly one `{{...}}` token resolves to the **native typed value** — a list stays a list, a number stays a number — so the operations receive the real object, not its string form. Optional in the schema, but in practice you set either `source` or the deprecated `source_key`; if neither resolves to a value, the node writes `null`.
</ParamField>

<ParamField path="operations" type="TransformOperation[]" required>
  The ordered list of operations to apply. Required. Each operation runs against the output of the previous one. See [the operation catalog](#the-operation-catalog) for every operation type and its params. An empty list passes the source through unchanged.
</ParamField>

<ParamField path="source_key" type="string" deprecated>
  Legacy direct state-key lookup (no `{{...}}`). Deprecated — use `source` with a `{{node_id.field}}` reference. Only consulted when `source` is unset.
</ParamField>

<ParamField path="output_key" type="string" deprecated>
  Deprecated and ignored. Output is always stored under the node id.
</ParamField>

### The `TransformOperation` object

Each entry in `operations` is one operation.

<ParamField path="type" type="string" required>
  The operation to perform — one of the values in [the operation catalog](#the-operation-catalog) below (for example `pick`, `map`, `parse_json`, `format_date`). An unrecognized value raises a validation error when the workflow is saved.
</ParamField>

<ParamField path="field" type="string">
  Optional dot-path that scopes the operation to one field of the current value when that value is an object — for example `field: "user.name"` applies the operation only to `user.name` and returns the (mutated) object. The dot-path navigates nested objects; if the final key is absent, the object is returned unchanged. When `field` is omitted, the operation applies to the whole current value. `field` has no effect when the current value is not an object.
</ParamField>

<ParamField path="params" type="object">
  Operation-specific parameters (for example `{ "keys": ["id", "title"] }` for `pick`). Optional; defaults to an empty object. Each operation below documents the `params` it reads. Inside string params, `{{node_id.field}}` references are resolved against run state for the operations noted below.
</ParamField>

## Inputs and outputs

<ResponseField name="Input" type="reference">
  The transformer reads exactly one value: whatever `source` resolves to. It does **not** take an `input_mapping` like the [tool](/workflow-builder/nodes/tool) or [function](/workflow-builder/nodes/function) nodes — to combine several earlier outputs, point `source` at one of them and pull in the others with the `merge`, `set`, `default`, `concat`, `template`, or `filter` operations, which resolve `{{node_id.field}}` references inside their params (see [combining data from multiple nodes](#combining-data-from-multiple-nodes)).
</ResponseField>

<ResponseField name="Output" type="any">
  The final value after every operation runs, written to run state under the node's `id`. The type depends on your pipeline: a string after `to_string`, a number after `count`, an object after `pick`, a list after `map`, and so on. Reference it downstream as `{{node_id}}` (or `{{node_id.path}}` for a sub-field).
</ResponseField>

<ResponseField name="Output (no source)" type="null">
  If `source` (and `source_key`) resolve to `null`, the node writes `null` and skips all operations.
</ResponseField>

<ResponseField name="Output (operation error)" type="object">
  If any operation raises, the node stops the pipeline and writes an error object instead of the transformed value: `{error, op}`, where `op` is the operation type that failed. The run continues — the transformer does not throw on operation errors (see [errors](#errors)).
</ResponseField>

## The operation catalog

Operations are grouped by what they act on. Each one takes the current pipeline value as input and returns the next value. Where an operation can read `{{node_id.field}}` references inside a param, it is called out — pure single-reference params keep their native type, mixed strings are templated to text.

<Note>
  Most operations are **type-guarded**: if the current value is not the type an operation expects (for example `pick` on a non-object, or `sum` on a non-list), the value is returned unchanged rather than erroring. The exceptions that can produce a fallback value instead are noted per operation.
</Note>

### String operations

<ParamField path="uppercase" type="operation">
  Uppercases the value as a string. No params.
</ParamField>

<ParamField path="lowercase" type="operation">
  Lowercases the value as a string. No params.
</ParamField>

<ParamField path="trim" type="operation">
  Strips leading and trailing whitespace from the value as a string. No params.
</ParamField>

<ParamField path="concat" type="operation">
  Joins values into one string. `params.values` is a list of additional values; `params.separator` (default `""`) joins them. The current value is placed first. A `values` entry that starts with `$` is read from state by name; one containing `{{...}}` is template-resolved. Everything is coerced to a string.
</ParamField>

<ParamField path="split" type="operation">
  Splits the value (as a string) into a list on `params.separator` (default `","`). The separator may contain `{{...}}` references.
</ParamField>

<ParamField path="replace" type="operation">
  Replaces every occurrence of `params.old` with `params.new` in the value as a string. Both `old` and `new` may contain `{{...}}` references.
</ParamField>

<ParamField path="template" type="operation">
  Renders `params.template`. First any `{{node_id.field}}` references in the template are resolved against state; then, if the current value is an object, each `{key}` placeholder is replaced with that object's matching field. Use this to build a string from an object's fields.
</ParamField>

<ParamField path="substring" type="operation">
  Returns `str(value)[start:end]` using `params.start` (default `0`) and optional `params.end`.
</ParamField>

### Object operations

<ParamField path="pick" type="operation">
  Keeps only the keys in `params.keys` (a list). Missing keys are skipped. Non-object values pass through unchanged.
</ParamField>

<ParamField path="omit" type="operation">
  Removes the keys in `params.keys` (a list), keeping everything else.
</ParamField>

<ParamField path="rename" type="operation">
  Renames keys using `params.mapping`, an `{old_key: new_key}` map. Keys not in the map keep their name.
</ParamField>

<ParamField path="merge" type="operation">
  Shallow-merges another object into the current object. `params.with` is the object (or a `$state_key`) to merge in; its string values that are `{{...}}` references are resolved (pure references keep their type). When the current value is an object and `with` is a non-object value, `params.as_key` nests it under that key.
</ParamField>

<ParamField path="flatten" type="operation">
  Flattens a nested object into a single level, joining nested keys with `params.separator` (default `"_"`) — for example `{a: {b: 1}}` becomes `{a_b: 1}`.
</ParamField>

<ParamField path="nest" type="operation">
  Wraps the value under a path. `params.path` (or `params.key`) like `"user.profile"` produces `{user: {profile: <value>}}`.
</ParamField>

<ParamField path="default" type="operation">
  Fills in missing or `null` fields of an object from `params.defaults` (an `{key: value}` map); existing non-null fields are kept. Default values may be `{{...}}` references. When the current value is not an object, returns the value if it is non-null, otherwise `params.value`.
</ParamField>

<ParamField path="set" type="operation">
  Sets `params.key` to `params.value` on the object (overwriting). The value may be a `$state_key` or a `{{...}}` reference (pure references keep their type). No-ops when the current value is not an object or `key` is empty.
</ParamField>

### Array operations

<ParamField path="map" type="operation">
  Applies a nested operation to every item of a list. `params.operation` is itself an operation object — `{type, params}` — for example `{ "type": "pick", "params": { "keys": ["id"] } }` to reduce each record to its id.
</ParamField>

<ParamField path="filter" type="operation">
  Keeps list items whose field matches a condition. `params.condition` has a `field` plus one matcher: `equals`, `not_equals`, `not_null`, `is_null`, `contains`, `greater_than`, `less_than`, `in`, `not_in`, or `exists`. Condition values may be `{{...}}` references. Only the first matcher present is applied; items that are not objects are dropped.
</ParamField>

<ParamField path="sort" type="operation">
  Sorts a list. `params.key` sorts a list of objects by that field; without a key it sorts scalars. `params.reverse` (default `false`) reverses the order.
</ParamField>

<ParamField path="first" type="operation">
  Returns the first item of a list, or `null` if the list is empty.
</ParamField>

<ParamField path="last" type="operation">
  Returns the last item of a list, or `null` if the list is empty.
</ParamField>

<ParamField path="count" type="operation">
  Returns the length of a list, object, or string; `0` for other types.
</ParamField>

<ParamField path="sum" type="operation">
  Sums a list of numbers. With `params.key`, sums that field across a list of objects.
</ParamField>

<ParamField path="avg" type="operation">
  Averages a list of numbers (or `params.key` across objects). Returns `0` for an empty list.
</ParamField>

<ParamField path="group_by" type="operation">
  Groups a list of objects into an object keyed by `params.key`'s value — `{group_value: [items...]}`.
</ParamField>

<ParamField path="unique" type="operation">
  De-duplicates a list. With `params.key`, keeps the first item per distinct field value; without a key, de-duplicates scalars.
</ParamField>

<ParamField path="reverse" type="operation">
  Reverses a list, or reverses a string character by character.
</ParamField>

<ParamField path="slice" type="operation">
  Returns `value[start:end]` of a list or string using `params.start` (default `0`) and optional `params.end`.
</ParamField>

### Type conversions

<ParamField path="to_string" type="operation">
  Converts the value to its string form (`""` for `null`). No params.
</ParamField>

<ParamField path="to_number" type="operation">
  Parses the value to a number (`int` when a string has no decimal point, else `float`). Returns `0` if it cannot be parsed.
</ParamField>

<ParamField path="to_boolean" type="operation">
  Converts to a boolean. A string is `true` when it is one of `true`, `1`, `yes`, or `on` (case-insensitive); otherwise standard truthiness applies.
</ParamField>

<ParamField path="to_array" type="operation">
  Wraps the value in a list. A list passes through; an object yields its values; a string yields its characters; any other value is wrapped as `[value]`.
</ParamField>

<ParamField path="parse_json" type="operation">
  Parses a JSON string into an object or list. If the string is not valid JSON, the original string is returned unchanged.
</ParamField>

<ParamField path="to_json" type="operation">
  Serializes the value to a JSON string (non-serializable values fall back to their string form).
</ParamField>

### Date operations

<ParamField path="format_date" type="operation">
  Formats a datetime or a Unix timestamp (seconds) using `params.format` (a strftime pattern, default `%Y-%m-%d %H:%M:%S`). The format may contain `{{...}}` references. Other value types pass through unchanged.
</ParamField>

<ParamField path="parse_date" type="operation">
  Parses a date string into an ISO-8601 string using `params.format` (default `%Y-%m-%d`). The format may contain `{{...}}` references. If the string does not match, it is returned unchanged.
</ParamField>

### Math operations

<ParamField path="math" type="operation">
  Evaluates `params.expression` with the current value bound to `x` (and `math` available) — for example `"x * 1.2"` or `"math.sqrt(x)"`. The expression may contain `{{...}}` references. On any evaluation error the current value is returned unchanged.
</ParamField>

<ParamField path="round" type="operation">
  Rounds a number to `params.decimals` decimal places (default `0`). Non-numbers pass through.
</ParamField>

<ParamField path="abs" type="operation">
  Returns the absolute value of a number. Non-numbers pass through.
</ParamField>

<ParamField path="min" type="operation">
  Returns the smallest item of a list (`null` for an empty list). Non-lists pass through.
</ParamField>

<ParamField path="max" type="operation">
  Returns the largest item of a list (`null` for an empty list). Non-lists pass through.
</ParamField>

<Warning>
  The `math` operation and the `filter` matchers evaluate expressions in a sandbox that removes Python built-ins, but they still use `eval`. Treat operation params as part of your workflow definition — author them yourself; do not build a `math` expression or a `filter` condition out of untrusted run input.
</Warning>

## Referencing data: `{{node_id.field}}`

The transformer's `source` is the main place you wire in an earlier node's output, and it follows the standard reference rules from [variables & references](/workflow-builder/variables-and-references):

* A `source` that is exactly one reference (for example `{{node_fetch.result.items}}`) resolves to the **native typed value**, so a list arrives as a list and an object as an object.
* The path supports dot and bracket access: `{{node_fetch.result.items[0].id}}`.
* An unresolved or out-of-range `source` resolves to `null`, which makes the node skip its operations and write `null`.

Beyond `source`, several operations resolve `{{...}}` references inside their params — `merge` (the `with` object), `set` and `default` (their values), `concat` (its `values`), `template` (the template body), `replace` (`old`/`new`), `split` (the separator), `filter` (condition values), and the date/math format and expression strings. This is how a single transformer can pull in data from more than one earlier node.

## Combining data from multiple nodes

A transformer reads one `source`, but its operation params can reference any other node. To merge two earlier outputs into one object, start from one of them and `merge` (or `set`) the other in by reference.

```json title="Merge two node outputs into one object" theme={null}
{
  "id": "node_combine",
  "type": "transformer",
  "name": "Combine profile and order",
  "transformer_config": {
    "source": "{{node_profile.result}}",
    "operations": [
      {
        "type": "merge",
        "params": {
          "with": {
            "latest_order_id": "{{node_order.result.id}}",
            "order_total": "{{node_order.result.total}}"
          }
        }
      }
    ]
  }
}
```

After this node runs, `{{node_combine}}` holds the profile object plus the two pulled-in order fields. Because `latest_order_id` and `order_total` are pure single references, they keep their native types (a string id and a numeric total).

## Worked example: shape a tool response for an LLM

This transformer sits between a [tool node](/workflow-builder/nodes/tool) that returns a list of support tickets and an [LLM node](/workflow-builder/nodes/llm) that summarizes them. It filters to open tickets, sorts them newest-first, reduces each ticket to the three fields the prompt needs, and counts them — turning a verbose API payload into a compact, prompt-ready structure.

<Steps>
  <Step title="A tool node fetches tickets">
    `node_tickets` calls a [tool](/workflow-builder/nodes/tool) and writes its unwrapped result under `node_tickets`, including a `result.tickets` array of ticket objects.
  </Step>

  <Step title="The transformer reshapes the list">
    `node_shape` reads `{{node_tickets.result.tickets}}`, then runs an ordered pipeline: `filter` to open tickets, `sort` by `created_at` descending, `map` each ticket down to `{id, subject, priority}`, and finally a parallel `count` is captured in a second transformer (or you read `{{node_shape}}` and its length downstream).
  </Step>

  <Step title="An LLM node summarizes the shaped data">
    `node_summary` reads `{{node_shape}}` into its prompt and writes the summary under `node_summary`.
  </Step>
</Steps>

```json title="Transformer node (pipeline of four operations)" theme={null}
{
  "id": "node_shape",
  "type": "transformer",
  "name": "Shape open tickets",
  "transformer_config": {
    "source": "{{node_tickets.result.tickets}}",
    "operations": [
      {
        "type": "filter",
        "params": { "condition": { "field": "status", "equals": "open" } }
      },
      {
        "type": "sort",
        "params": { "key": "created_at", "reverse": true }
      },
      {
        "type": "map",
        "params": {
          "operation": {
            "type": "pick",
            "params": { "keys": ["id", "subject", "priority"] }
          }
        }
      }
    ]
  }
}
```

Given a `source` of:

```json title="Resolved source — {{node_tickets.result.tickets}}" theme={null}
[
  { "id": "T-1", "subject": "Login fails",  "priority": "high", "status": "open",   "created_at": "2026-06-19T09:00:00Z", "assignee": "ana" },
  { "id": "T-2", "subject": "Typo in docs", "priority": "low",  "status": "closed", "created_at": "2026-06-18T12:00:00Z", "assignee": "bo" },
  { "id": "T-3", "subject": "Slow export",  "priority": "med",  "status": "open",   "created_at": "2026-06-20T08:00:00Z", "assignee": "cy" }
]
```

the node writes this under `node_shape`:

```json title="Output — {{node_shape}}" theme={null}
[
  { "id": "T-3", "subject": "Slow export", "priority": "med" },
  { "id": "T-1", "subject": "Login fails", "priority": "high" }
]
```

A later [LLM node](/workflow-builder/nodes/llm) then reads it directly:

```json title="LLM node reading the shaped list" theme={null}
{
  "id": "node_summary",
  "type": "llm",
  "name": "Summarize open tickets",
  "llm_config": {
    "llm": {
      "integration_name": "modulexai",
      "provider_id": "anthropic",
      "model_id": "claude-haiku-3.5"
    },
    "system_prompt": "You summarize support queues for an on-call engineer.",
    "user_prompt": "Summarize these open tickets, highest priority first:\n{{node_shape}}"
  }
}
```

<MediaEmbed id="MX-MEDIA-3141" type="image" caption={"Diagram of the four-operation transformer pipeline turning a raw ticket list into a compact, prompt-ready list."} />

### Run it

Run the workflow over the API or an SDK and stream it to watch each `node_update` arrive — one per node, keyed by `node` with its per-node `output` — followed by `done`. The transformer's `node_update` carries the shaped value under its node id. See [run via API](/workflow-builder/execution/api-endpoint) and [SSE run streaming](/realtime/sse-streaming) for the full event taxonomy.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.modulex.dev/workflows/run" \
    -H "Authorization: Bearer mx_live_xxx" \
    -H "X-Organization-ID: org_xxx" \
    -H "Content-Type: application/json" \
    -d '{
      "workflow_id": "wf_xxx",
      "input": "support queue"
    }'
  ```

  ```python Python theme={null}
  from modulex import ModuleX

  client = ModuleX(
      api_key="mx_live_xxx",
      organization_id="org_xxx",
  )

  run = client.workflows.run(
      workflow_id="wf_xxx",
      input="support queue",
  )
  print(run)
  ```

  ```javascript JavaScript theme={null}
  import { ModuleX } from "modulex";

  const client = new ModuleX({
    apiKey: "mx_live_xxx",
    organizationId: "org_xxx",
  });

  const run = await client.workflows.run("wf_xxx", {
    input: "support queue",
  });
  console.log(run);
  ```
</CodeGroup>

<Warning>
  The run surface is behind the [billing gate](/billing/usage-gating). When an organization is out of credits or over a limit, the run endpoint returns a flat denial envelope — `{code, layer, key, current, limit, reason}` — as **402 / 403 / 429**, not the plain `{detail}` shape used by CRUD routes. The transformer node itself never triggers the gate (it consumes no credits), but an [LLM](/workflow-builder/nodes/llm), [agent](/workflow-builder/nodes/agent), or managed [knowledge](/workflow-builder/nodes/knowledge) node elsewhere in the same workflow can. See [errors & status codes](/api-reference/errors) for all three envelope shapes.
</Warning>

## Errors

The transformer is resilient by design: most operations fall back to passing the value through rather than failing, and any operation that does raise is caught.

<ResponseField name="Missing source" type="warning">
  If `source` (and the deprecated `source_key`) resolve to `null`, the node logs a warning and writes `null` — it does not raise. Downstream references like `{{node_id.field}}` then resolve to `null`.
</ResponseField>

<ResponseField name="Operation failure" type="object">
  If an operation raises mid-pipeline, the node stops and writes `{error, op}` (the message and the failing operation type) under its node id. The run continues to the next node — the transformer does not propagate the exception, so its [retry policy](/workflow-builder/error-handling-retries) is not triggered by operation errors.
</ResponseField>

<ResponseField name="Type mismatch" type="passthrough">
  An operation applied to the wrong type (for example `sum` on a non-list, `pick` on a non-object) returns the value unchanged rather than erroring. Parse-style operations have their own fallbacks: `to_number` returns `0`, `parse_json` returns the original string, and `math` returns the input on an evaluation error.
</ResponseField>

<ResponseField name="Missing config" type="ValueError">
  A `transformer` node with no `transformer_config` raises `Transformer node <id> missing transformer_config` when the workflow is compiled — caught at validation time, before the run starts.
</ResponseField>

Because operation errors surface as data (`{error, op}`) rather than as a thrown exception, guard against them downstream: branch on the presence of an `error` key with a [conditional node](/workflow-builder/nodes/conditional), or validate the shape with a [guardrails node](/workflow-builder/nodes/guardrails). For how node failures surface and how the run-level retry policy works for nodes that do throw, see [error handling & retries](/workflow-builder/error-handling-retries).

## Credit impact

A transformer node consumes **no [credits](/billing/credits)**. It runs entirely inside the engine — there is no model call, no managed retrieval, and no external request — so it is never metered and never hits the [billing gate](/billing/usage-gating). Use transformers freely to keep model prompts small and well-shaped, which lowers the [token cost](/billing/credits) of the [LLM](/workflow-builder/nodes/llm) and [agent](/workflow-builder/nodes/agent) nodes that read their output.

## Related

<CardGroup cols={2}>
  <Card title="Node types reference" icon="diagram-project" href="/workflow-builder/nodes/overview">
    All nine node types and the output-to-state convention.
  </Card>

  <Card title="Variables & references" icon="brackets-curly" href="/workflow-builder/variables-and-references">
    The full `{{node_id.field}}` resolution rules and the run state model.
  </Card>

  <Card title="Conditional node" icon="split" href="/workflow-builder/nodes/conditional">
    Branch on a transformer's output, or loop over a transformed list.
  </Card>

  <Card title="Function node" icon="code" href="/workflow-builder/nodes/function">
    HTTP requests, webhooks, and JSON-schema validation as built-ins.
  </Card>

  <Card title="Error handling & retries" icon="triangle-exclamation" href="/workflow-builder/error-handling-retries">
    How node failures surface and how the retry policy behaves.
  </Card>

  <Card title="Workflow engine & nodes" icon="gear" href="/concepts/workflow-engine">
    How nodes compile into a graph, the run state, edges, and loops.
  </Card>
</CardGroup>
