Beta You're reading the docs for Kubb v5, which is currently in beta. View the stable v4 docs
Skip to content

Migration: @kubb/plugin-react-query

Part of the v4 → v5 migration guide. For the full option reference, see @kubb/plugin-react-query.

resolver.resolveName replaces transformers.name. The v4 transformers object held only name, so that is the whole rename. To rewrite generated nodes before printing, use the new macros option.

client is a selector, not an object

In v4 the client option carried the whole client config, including dataReturnType, clientType, baseURL, bundle, and the custom importPath. v5 drops the object form. The hooks no longer emit their own client. They call a registered client plugin instead, so you register @kubb/plugin-axios or @kubb/plugin-fetch and point client at it with the string 'axios' or 'fetch'. When exactly one client plugin is registered, leave client off and the plugin picks it up. Set the string only to disambiguate when both are registered.

typescript
import { defineConfig } from '@kubb/core'
import { pluginReactQuery } from '@kubb/plugin-react-query'

export default defineConfig({
  plugins: [
    pluginReactQuery({
      client: { client: 'axios', dataReturnType: 'data', baseURL: 'https://api.example.com' },
    }),
  ],
})
typescript
import { defineConfig } from 'kubb'
import { pluginTs } from '@kubb/plugin-ts'
import { pluginAxios } from '@kubb/plugin-axios'
import { pluginReactQuery } from '@kubb/plugin-react-query'

export default defineConfig({
  plugins: [
    pluginTs(),
    pluginAxios({ baseURL: 'https://api.example.com' }),
    pluginReactQuery({ client: 'axios' }),
  ],
})

dataReturnType has no replacement on the query plugin. The client plugin returns the response body, so the hooks read res.data. Move baseURL to the client plugin, and see Migration: @kubb/plugin-client removed for the clientType, bundle, and importPath options that went with it.

Renamed: parservalidator

The parser option is now validator. Set validator: 'zod' where you previously set parser: 'zod'. The accepted values are unchanged: false, 'zod', or { request: 'zod', response: 'zod' } to validate request and response bodies with schemas from @kubb/plugin-zod.

Diff
diff
  pluginReactQuery({
   parser: 'zod',
   validator: 'zod',
  })

Removed: generators

The generators option is gone. Plugins no longer accept extra generators inline. Move custom output into your own plugin. See Creating plugins.

Removed: paramsType, pathParamsType, paramsCasing

These three options are gone, including client.paramsCasing. Each hook now takes its parameters as a single grouped options object shaped as { path, query, body, headers }, with camelCase property names. This matches the shape @kubb/plugin-fetch already used. Query params move under query, path params under path, the request body under body, and header params under headers.

Diff
diff
  pluginReactQuery({
   paramsType: 'object',
   pathParamsType: 'object',
   paramsCasing: 'camelcase',
  })

Update the call sites. Query params move into query, and path params move into path. When an operation has a required parameter in a group, that group (path, query, or headers) is required too, so an incomplete call fails to compile.

typescript
useFindPets({ status: 'available' })
useGetPet(petId)
useUpdatePet().mutate({ petId, data: pet })
typescript
useFindPets({ query: { status: 'available' } })
useGetPet({ path: { petId } })
useUpdatePet().mutate({ path: { petId }, body: pet })

The first argument is typed Omit<XxxRequestConfig, 'url'>, the RequestConfig type @kubb/plugin-ts generates. The trailing config argument is unchanged.

Generated output

@kubb/plugin-vue-query shares these changes. Both plugins also pick up the renames from the client plugin (*Data, *Response, *Status<code>).

The exported *MutationKey type alias is gone. Use the runtime helper when you need the key:

Diff
diff
 export type CreateUserMutationKey = ReturnType<typeof createUserMutationKey>
 export const createUserMutationKey = () => [{ url: '/user' }] as const
 export const createUserMutationKey = () => [{ url: '/user' }] as const

Mutation and query TData narrows to 2xx responses

The TData generic on useMutation, useQuery, useInfiniteQuery, useSuspenseQuery, and their *Options helpers now points at the union of 2xx response status types instead of the full response alias. That matches TanStack Query's contract, where TData is the resolved success value and errors flow through TError.

Diff
diff
  export function useAddPet<TContext>(
    options: {
      mutation?: MutationObserverOptions<
       AddPetResponse,
       AddPetStatus200,
        ResponseErrorConfig<AddPetStatus405>,
        { data: AddPetData },
        TContext
      > & { client?: QueryClient }
      client?: Partial<RequestConfig<AddPetData>> & { client?: typeof client }
    } = {},
  ) { /* ... */ }

Call sites that previously needed as casts or 'id' in res checks compile directly:

Generated output
typescript
const pet = await mutateAsync({ body: { name: 'Rex' } })
pet.id // typed as Pet.id, no narrowing required

The change covers queryFn, queryOptions, and the hook generics together. No config flag brings back the old behavior. If your client returns non-2xx bodies as resolved data instead of throwing, wrap it to throw so TanStack Query's error / onError path fires. The previous typing was silently broken at runtime.

No auto enabled guard

v4 generated an enabled guard from the required path and query parameters: enabled: !!path?.petId in React Query, enabled: () => !!toValue(path?.petId) in Vue Query. Those parameters were already required, so !!path.petId was always true. The guard disabled nothing. It read like a safety net and did no work.

v5 removes it. The path, query, and headers groups are required in the generated queryKey, queryOptions, and hook signatures whenever the operation has a required parameter in that group, and nothing emits enabled for you. The query key types only the groups it reads, so a required headers parameter never leaks onto the key.

Diff
diff
  export function getPetByIdQueryOptions({ path }: Omit<GetPetByIdRequestConfig, 'url'>, config: Partial<RequestConfig> & { client?: Client } = {}) {
    const queryKey = getPetByIdQueryKey({ path })
    return queryOptions<GetPetByIdStatus200, ResponseErrorConfig<GetPetByIdStatus400 | GetPetByIdStatus404>, GetPetByIdStatus200, typeof queryKey>({
     enabled: !!path?.petId,
      queryKey,
      queryFn: async ({ signal }) => {
        return getPetById({ path }, { ...config, signal: config.signal ?? signal })
      },
    })
  }

To defer or disable a query, set TanStack Query's own enabled (or pass skipToken) through the hook options:

Generated output
typescript
// keep the query disabled until petId resolves
useGetPetById({ path: { petId } }, { query: { enabled: !!petId } })

NOTE

Suspense hooks always run, so they never had an enabled guard and are unchanged.

hooks defaults to false

The hooks option controls whether use* functions are emitted alongside the factory helpers. Its default changed from true to false, so existing configs that relied on generated hooks must now opt in explicitly.

kubb.config.ts
diff
  pluginReactQuery({
    output: { path: './hooks' },
   hooks: true,
  })

With hooks: false (the default) the plugin still emits queryOptions, mutationOptions, queryKey, and mutationKey. Only the useQuery, useSuspenseQuery, useInfiniteQuery, useSuspenseInfiniteQuery, and useMutation wrappers are skipped.