Skip to content
How-to guides

Call operations

Use the TanStack Query hooks Kubb generates from your OpenAPI spec. Pass typed path, query, and header parameters and read data and error state off the hook.

@kubb/plugin-react-query turns each operation into a hook that wraps the client function from @kubb/plugin-axios or @kubb/plugin-fetch. Read operations become useFoo, write operations become useFoo mutations, and every hook is typed from the spec.

By default the plugin emits only the factory helpers (queryOptions, mutationOptions, queryKey, mutationKey). Set hooks: true in the plugin options to also generate the use* hooks shown below.

Queries

A query hook takes the operation's grouped request config (path, query, headers, whichever the operation declares) as its first argument and returns a TanStack UseQueryResult:

import { useGetPetById } from './gen/hooks/useGetPetById'

const { data, error, isLoading } = useGetPetById({ path: { petId: 1n } })

The second argument holds two option groups. query takes any TanStack Query option plus a client to target a specific QueryClient. client takes per-call request config for the underlying client, such as baseURL or signal:

import { useGetPetById } from './gen/hooks/useGetPetById'

useGetPetById(
  { path: { petId: 1n } },
  {
    query: { staleTime: 60_000 },
    client: { baseURL: 'https://api.example.com/v1' },
  },
)

Factories

Every operation also exports a queryOptions factory and a queryKey helper, so you can prefetch, seed, or compose outside a hook. The key includes the operation URL and path parameters, followed by query or body values when present:

import { useQueryClient } from '@tanstack/react-query'
import { getPetByIdQueryKey, getPetByIdQueryOptions } from './gen/hooks/useGetPetById'

const queryClient = useQueryClient()

await queryClient.prefetchQuery(getPetByIdQueryOptions({ path: { petId: 1n } }))
queryClient.invalidateQueries({ queryKey: getPetByIdQueryKey({ path: { petId: 1n } }) })

Mutations

A mutation hook takes only an options object. The grouped request config is the mutation variable, so you pass it to mutate or mutateAsync:

import { useQueryClient } from '@tanstack/react-query'
import { useDeletePet } from './gen/hooks/useDeletePet'

const queryClient = useQueryClient()

const { mutate } = useDeletePet({
  mutation: { onSuccess: () => queryClient.invalidateQueries() },
})

mutate({ path: { petId: 1n } })

A mutationOptions factory and mutationKey helper are exported next to the hook, mirroring the query factories.

Errors and transport

The generated hooks call the client operation with throwOnError: true, so failures surface through the query library's error state, typed from the spec's error responses. Transport concerns (base URL, authentication, interceptors, serialization, validation) live on the client plugin: see the plugin-fetch or plugin-axios guides.

Customize cache keys

Set queryKey to change generated keys. String entries are emitted as source code, so use JSON.stringify for a string literal.

kubb.config.ts
import { pluginReactQuery } from '@kubb/plugin-react-query'

pluginReactQuery({
  queryKey: ({ node }) => [JSON.stringify(node.operationId)],
})

This produces a fixed key such as ['getUserByName'], independent of arguments. Include relevant path and query parameters in the key when their values identify different resources.

Load more pages

Configure infinite with a query parameter declared by the operation, its initial value, and the response path for the next cursor.

kubb.config.ts
import { pluginReactQuery } from '@kubb/plugin-react-query'

pluginReactQuery({
  hooks: true,
  infinite: {
    queryParam: 'page',
    initialPageParam: 0,
    nextParam: 'pagination.next.cursor',
  },
})

Only operations with a page query parameter receive infinite-query output. Change nextParam to match your API's response.

Use the generated factory with TanStack Query:

usage.ts
import { useInfiniteQuery } from '@tanstack/react-query'
import { findPetsByTagsInfiniteQueryOptions } from './gen/hooks/useFindPetsByTagsInfinite'

const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
  findPetsByTagsInfiniteQueryOptions({ query: { tags: ['dog'] } }),
)

Use suspense

Set suspense: {} and hooks: true to generate suspense hooks alongside regular hooks. Suspense requires TanStack Query v5 or higher.

kubb.config.ts
import { pluginReactQuery } from '@kubb/plugin-react-query'

pluginReactQuery({ hooks: true, suspense: {} })
usage.ts
import { useGetPetByIdSuspense } from './gen/hooks/useGetPetByIdSuspense'

const { data } = useGetPetByIdSuspense({ path: { petId: 1n } })

Share hook options

Set customOptions to call one options function from every generated hook.

kubb.config.ts
import { pluginReactQuery } from '@kubb/plugin-react-query'

pluginReactQuery({
  hooks: true,
  customOptions: {
    importPath: './useCustomHookOptions',
    name: 'useCustomHookOptions',
  },
})

Place the function where the generated import resolves. Each hook passes { hookName, operationId }. The generated barrel re-exports HookOptions.

src/gen/hooks/useCustomHookOptions.ts
import type { HookOptions } from './HookOptions'

export function useCustomHookOptions(
  { hookName }: { hookName: keyof HookOptions; operationId: string },
) {
  if (hookName === 'useGetPetById') {
    return { staleTime: 60_000 } satisfies HookOptions['useGetPetById']
  }
  return {}
}

Per-call query options override shared options.