Generate typed query options, mutations, pagination, cache keys, and invalidation helpers.
Use this when React Query owns your server-state cache. The generated companion gives every operation a typed query key and fetch function, so components do not recreate either by hand.
Keep the three generated files together in config:
import { defineConfig } from "typed-openapi";
export default defineConfig({ input: "./openapi.yaml", output: "./src/api/openapi.ts", defaultFetcher: "api.client.ts", tanstack: "query.ts",});Or generate the companion from the command line:
pnpm add @tanstack/react-querypnpm exec typed-openapi openapi.yaml \ --output src/api/openapi.ts \ --default-fetcher \ --tanstack query.tsThe generated module is intentionally framework-light: it creates typed query and mutation options. You still own your QueryClient, hooks, cache policy, and component boundaries.
Create a typed query
Section titled “Create a typed query”import { useQuery } from "@tanstack/react-query";import { TanstackQueryApiClient } from "../api/query";import { api } from "../api/api.client";
const queryApi = new TanstackQueryApiClient(api);
export function Pet({ petId }: { petId: number }) { const query = useQuery( queryApi.get("/pet/{petId}", { path: { petId } }).queryOptions, );
return <pre>{JSON.stringify(query.data, null, 2)}</pre>;}Construct TanstackQueryApiClient once per API client instance. The endpoint call returns a stable options object; pass that object to useQuery, server prefetching, or ensureQueryData instead of hand-writing keys and fetch functions.
The same generated call exposes suspenseQueryOptions for Suspense-based routes and works with fetchQuery() or ensureQueryData() outside React.
const options = queryApi.get("/pet/{petId}", { path: { petId: 7 } }).suspenseQueryOptions;const pet = await queryClient.ensureQueryData(options);Create a typed mutation
Section titled “Create a typed mutation”mutationOptions contains a correctly typed mutationFn and status-aware error type.
import { useMutation } from "@tanstack/react-query";
const createPet = useMutation( queryApi.mutation("post", "/pet", { withResponse: true }).mutationOptions,);
createPet.mutate( { body: { id: 7, name: "Mochi" } }, { onSuccess(result) { if (result.ok) console.log(result.data.name); else if (result.status === 400) console.error(result.data); }, },);Infinite queries and cache invalidation
Section titled “Infinite queries and cache invalidation”When an endpoint accepts a page parameter, opt into the generated infinite-query helper and name that parameter:
const feed = queryApi.get("/pets", { query: { limit: 20 } });
const options = feed.infiniteQueryOptions({ initialPageParam: 0, pageParamKey: "page", getNextPageParam: (lastPage) => lastPage.nextPage,});Use stable keys outside components too:
import { invalidateEndpoint, queryKeyFactory } from "../api/query";
await invalidateEndpoint(queryClient, "/pet/findByStatus", { query: { status: "available" },});
const key = queryKeyFactory.endpoint("/pet/findByStatus", { query: { status: "available" },});Effect-native clients work here too: the generated wrapper executes their operations with Effect.runPromise.
Use invalidateEndpoint() or queryKeyFactory after a mutation. They encode the same path and parameter shape as the query, which prevents cache misses caused by manually assembled keys.