Getting started

TypeScript

Where the types come from, and what to do when inference surprises you.

Every type at a call site is derived from the endpoint declaration. responseSchema gives you the result type, requestSchema the argument type, and pathParamsSchema the shape withPathParams accepts. You should not need to annotate anything.

Capabilities are gated at compile time

Members that would be wrong are typed never rather than missing. A POST endpoint has no usable useQuery, and an endpoint with an unbound required path parameter has no usable fetch, because the URL could not be built.

api.users.update.useQuery;
//               ^? never — update is a PATCH

api.users.detail.fetch;
//               ^? never — :id is not bound yet

api.users.detail.withPathParams({ id: '1' }).fetch;
//                                           ^? (request?, options?) => Promise<User>

Transforms resolve to their output type

A schema that transforms gives you the type after the transform, not before. This holds for both supported protocols.

responseSchema: z.string().transform(Number)
// data is number, not string

Reading the cache with types

Use queryOptions().queryKey when you want a typed cache read. It carries TanStack’s DataTag brand, so getQueryData returns the response type. The plain queryKey member is deliberately untagged, because it is used as an invalidation prefix as often as a whole key.

qc.getQueryData(ep.queryOptions().queryKey);
//  ^? User | undefined

qc.getQueryData(ep.queryKey);
//  ^? unknown — this is a prefix, not a key