transform
Reshape a response before pick and validation — and reshape a request on the way out, through its input schema.
Use transform when the raw response isn't yet the shape the rest of the
pipeline expects — scrape HTML text into a structured object, or flatten an odd
envelope — so a single function reshapes the body before pick and validation
ever see it.
Example
import { } from 'stitchapi';
const = ({
: 'https://api.example.com',
: '/listings',
// The body is `unknown` — narrow it, then reshape the raw envelope into a
// plain array before pick and validation run.
: () => ( as { : unknown[] }).,
});The raw body arrives as a wrapped { results: [...] }; transform returns the
inner array, and everything downstream works against that array instead.
Options
transform?: (body: unknown) => unknown runs first in the response
pipeline — before pick and before
validation (and
drift). That ordering is the point: reshape a
raw body — scrape HTML text into a structured object, or flatten an odd
envelope — into the shape pick and validation are written for, rather than
bending them around the source's quirks.
body is typed unknown, so narrow it before reading (a cast or a type guard)
and return any reshaped value. See
Reference → Config types for every field.
Anti-pattern: don't reach for transform to pull the inner field out of
an envelope — a cast like reading .results off the body is an opaque
function whose output is unchecked, so a wrong narrowing slips past and only
surfaces later as drift. Name the path with pick instead, which is
declarative, round-trips as JSON, and feeds validation and drift directly.
See pick.
Reshaping the request
transform is a response slot. To reshape a body on the way out — your
field names in, the API's on the wire — put the mapping in the input schema.
A declared slot doesn't only validate: the request is built from the value the
schema returned, so a schema that transforms is a request transform.
import { } from 'stitchapi';
import { } from 'zod';
const = ({
: 'POST',
: 'https://api.example.com',
: '/users',
: {
// Callers pass the house shape; the API's shape goes on the wire.
:
.({ : .(), : .() })
.(() => ({
: .,
: ..(),
})),
},
});
await ({ : { : 'mango', : new () } });The call argument is typed from the schema's input side, so callers keep
{ userName, joinedAt } and never see { user_name, joined_at }. The mapping
lives on the stitch once, instead of at every call site.
It runs in the right place, too: input schemas resolve before the request is
built, so the reshaped body is what auth signs and what the cache key is
derived from. Every slot works this way — params, query, body, headers,
and GraphQL variables — and each is transformed independently.
Why transform is response-only
An output schema can reshape too, by the same rule: the stitch serves the
value the schema returned. So why does transform exist as its own slot — and
why only on the response?
Because on the response side the reshaping has to happen somewhere a schema can't reach, and has to stay visible to machinery a schema would hide it from:
- Ordering.
transformruns beforepick, sopickaddresses the reshaped body. A schema's own transform runs at validation — afterpickhas already read the raw shape. - Drift. Drift is anchored on the
difference between the raw body and the validated value. A transform inside a
drift()schema sits on the validated side, so every field it renames is reported as drift. - Caching. A
transformis a declared opacity: a stitch that has one refuses to cache until you setcache.fingerprint.transform, because re-validation cannot detect that the transform itself changed. A transform buried insideoutputis invisible to that check, so it silently defeats it.
None of the three has a request-side counterpart. There is no request pick and
no request drift, and the cache key is derived from the request after the input
schema shaped it, so the reshaping is already folded in. A request transform
slot would add a second spelling and no guarantee — which is why there isn't one.
The corollary for the response side: prefer transform over a transform
inside output. Keeping the two apart is what keeps pick, drift, and the
cache fingerprint honest about what reshaped the body.