Skip to main content
The @orpc/tanstack-query package provides deep TanStack Query (v5) integration for oRPC. It generates fully typed queryOptions, infiniteOptions, and mutationOptions from your router client.

Installation

npm install @orpc/tanstack-query @tanstack/query-core

createRouterUtils(client, options?)

Creates a router utils object from a client. The result mirrors the structure of your router, replacing each procedure with a ProcedureUtils object.
import { createRouterUtils } from '@orpc/tanstack-query'
import { orpc } from './client'

export const utils = createRouterUtils(orpc)

Options

path
readonly string[]
Base path for all operations. Useful when creating utils for a sub-router.
experimental_defaults
experimental_RouterUtilsDefaults<T>
Default options applied to all operations in the router utils.

ProcedureUtils

Each procedure in the router utils exposes the following methods:

.call(input, options?)

Directly calls the procedure client.
const planet = await utils.planet.find.call({ id: 1 })

.queryKey(options?)

Generates a full matching TanStack Query key for this procedure.
const key = utils.planet.list.queryKey({ input: { limit: 10 } })
// => ['planet', 'list', { input: { limit: 10 }, type: 'query' }]

.queryOptions(options?)

Generates useQuery / useSuspenseQuery compatible options.
const { data } = useQuery(
  utils.planet.list.queryOptions({ input: { limit: 10 } })
)

// With skipToken (disable query):
const { data } = useQuery(
  utils.planet.find.queryOptions({
    input: userId ? { id: userId } : skipToken,
  })
)

.infiniteKey(options)

Generates a TanStack Query key for infinite queries.

.infiniteOptions(options)

Generates useInfiniteQuery / useSuspenseInfiniteQuery compatible options.
const { data } = useInfiniteQuery(
  utils.planet.list.infiniteOptions({
    input: (pageParam: number) => ({ cursor: pageParam, limit: 20 }),
    initialPageParam: 0,
    getNextPageParam: (lastPage, pages) => pages.length,
  })
)

.mutationKey(options?)

Generates a TanStack Query mutation key.

.mutationOptions(options?)

Generates useMutation compatible options.
const mutation = useMutation(
  utils.planet.create.mutationOptions({
    onSuccess: (data) => {
      console.log('Created planet:', data)
    },
  })
)

mutation.mutate({ name: 'Mars', description: 'The red planet' })

Streaming and event iterator utilities

For procedures that return event iterators, use the streamed or live query utilities.

.experimental_streamedKey(options?)

Generates a key for streamed queries.

.experimental_streamedOptions(options?)

Generates options for streaming queries. Each chunk is appended to the result array.
const { data } = useQuery(
  utils.events.stream.experimental_streamedOptions({
    input: { topic: 'updates' },
  })
)
// data is an array of accumulated chunks

.experimental_liveKey(options?)

Generates a key for live queries.

.experimental_liveOptions(options?)

Generates options for live queries. Each chunk replaces the entire result (no accumulation).
const { data } = useQuery(
  utils.events.ticker.experimental_liveOptions({
    input: { symbol: 'BTC' },
  })
)
// data is the latest chunk

RouterUtils type

The type of the value returned by createRouterUtils:
type RouterUtils<T extends NestedClient<any>>
  = T extends Client<infer UClientContext, infer UInput, infer UOutput, infer UError>
    ? ProcedureUtils<UClientContext, UInput, UOutput, UError> & GeneralUtils<UInput>
    : {
        [K in keyof T]: T[K] extends NestedClient<any> ? RouterUtils<T[K]> : never
      } & GeneralUtils<unknown>

GeneralUtils

Every node in the router utils (both procedures and router groups) exposes a key() method for generating partial matching query keys — useful for cache invalidation and query filtering.

.key(options?)

Generates a partial matching key for actions like revalidating queries, checking mutation status, etc.
import { useQueryClient } from '@tanstack/react-query'

const queryClient = useQueryClient()

// Invalidate all planet queries (all types, all inputs):
await queryClient.invalidateQueries({
  queryKey: utils.planet.key(),
})

// Invalidate all planet.list queries specifically:
await queryClient.invalidateQueries({
  queryKey: utils.planet.list.key({ type: 'query' }),
})

// Invalidate with specific input:
await queryClient.invalidateQueries({
  queryKey: utils.planet.list.key({ type: 'query', input: { limit: 10 } }),
})
The key() method is available at every level of the router utils tree, making it easy to invalidate broad or narrow sets of queries.

React setup example

// orpc.ts
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import { createRouterUtils } from '@orpc/tanstack-query'
import type { RouterClient } from '@orpc/server'
import type { router } from './server/router'

export const link = new RPCLink({ url: '/api' })
export const orpc: RouterClient<typeof router> = createORPCClient(link)
export const utils = createRouterUtils(orpc)
// PlanetList.tsx
import { useQuery } from '@tanstack/react-query'
import { utils } from './orpc'

export function PlanetList() {
  const { data } = useQuery(
    utils.planet.list.queryOptions({ input: { limit: 10 } })
  )
  return <ul>{data?.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}

Build docs developers (and LLMs) love