Unify Logo Footer.svg
Unify Applications
Logo
Response Transforms

Response Transforms

Logo

5 mins READ

Response Transforms

A response transform is an optional JavaScript step on a data source that reshapes the response before any block reads it. The API returns whatever shape it returns; the transform turns that into the shape your page actually wants — flattening nesting, renaming fields, filtering rows, computing derived values — so every consuming block binds to clean data instead of each block untangling the raw payload itself.

How It Works

The transform is a JavaScript script attached to the data source. The fetched response is handed to your code as a variable named data, and whatever your code returns becomes the data source's data — the value blocks bind to. The editor starts with a pass-through that is treated as "no transform":

// Query response is available as the `data` variable return data;

Leaving this starter code unchanged means no transform is active. Edit it — even slightly — and the full transform machinery switches on.

Transform Properties

PropertyDescription
Input variable (data)The raw response from the fetch. Exactly what the API or platform returned, before any reshaping.
Return valueWhatever your code returns becomes the data source's data. Blocks bind to this value, not the raw response.
Other page bindingsYour transform code can reference other page values through binding expressions ({{ variable.value }}). Their current values are injected as parameters when the transform runs.
rawDataThe untouched original response is always available alongside the transformed result via the data source's rawData key, regardless of whether a transform runs.

Note: The transform executes as part of every run — including polls, retries, and action-triggered refreshes. Blocks always see the transformed shape; there is no flash of raw data first.

Common Transform Patterns

Pattern 1 — Flatten nested data

// Raw response: { items: { records: [ ... ] } } // Transform to a flat array: return data.items.records;

Pattern 2 — Rename fields

// API uses snake_case; components expect camelCase return data.map(item => ({ id: item.order_id, customerName: item.customer_name, totalAmount: item.total_amount, status: item.order_status }));

Pattern 3 — Compute derived fields

// Add a computed field to each record return data.map(item => ({ ...item, displayLabel: `${item.firstName} ${item.lastName}`, isOverdue: new Date(item.dueDate) < new Date() }));

Pattern 4 — Filter records using a page variable

// Filter by a value from a page variable (injected as a binding) // Note: changing this variable does NOT re-run the data source. // The filter runs against the already-fetched data. const minAmount = {{ min_amount_var.value }}; return data.filter(item => item.total >= minAmount);

Pattern 5 — Defensive transform (handle missing fields)

// Guard against missing or unexpected shapes const records = data?.records ?? []; return records.map(item => ({ id: item.id, name: item.name ?? 'Unnamed', tags: item.tags ?? [] }));

Where the Result Appears

With a transform configured, the run output changes:

KeyContentBinding expression
dataThe value your transform returned.{{ orders_query.data }}
rawDataThe untransformed original response. Available even after transforming.{{ orders_query.rawData }}

Warning: The data source's output schema — what the binding picker offers when you map fields into blocks — follows the transformed value, not the raw response. If you add a transform after wiring blocks, bindings to paths that no longer exist will show empty values.

When the Transform Fails

The transform is all-or-nothing: if your code throws an error, the entire data source run fails. Blocks see the error state, failure events fire, and the raw response is not exposed. A bug in the transform looks exactly like a failed network fetch to the rest of the page.

Warning: Always write defensive code in transforms. Use optional chaining (data?.field) and nullish coalescing (?? []) to handle missing or unexpected fields — an exception in the transform hides what may be a perfectly good API response.

Transforms and Reactivity

Bindings used only inside the transform code do not make the data source reactive to those values. The data source re-runs when its request inputs change — a page value referenced solely in the transform is not tracked as a dependency.

If you need the data source to re-run when a transform's input changes:

  1. Bind the value into one of the request's inputs (even as an unused parameter), or

  2. Filter in the consuming block instead of in the transform code.

Note: A transform containing exactly return data; (the initial snippet) is treated as unconfigured — the platform skips the transform step entirely. The wrap behavior (putting the result under a result key) only activates when the code has been meaningfully edited.

Transforms and Infinite Loading

On an infinite-loading data source, the transform applies to each fetched page separately. The pages' transformed lists are then combined into the single list blocks consume. Ensure the transform returns the full page object including paging signals (has-more flag or total count) — a transform that returns only the items array drops the signal, and "load more" silently stops after the first page.

When to Use a Transform

Use a transform when…Consider alternatives when…
The API response structure doesn't match what your components expect.A platform query can narrow fields server-side — fewer fields, no transform needed.
You need to flatten, rename, or merge fields across the response.Only one or two blocks need the reshaped value — transform in the consuming block's expression instead.
You need derived computed fields (totals, labels, flags) on every record.The raw shape is fine and you only need to filter — consider filtering in a consuming expression rather than re-running the source.