Skip to main content
@orpc/tanstack-query provides framework-agnostic utilities that generate fully typed TanStack Query options from your oRPC client. Framework-specific packages (@orpc/react-query, @orpc/vue-query, etc.) re-export these utilities with framework-appropriate types.

Installation

npm install @orpc/tanstack-query @tanstack/query-core
For React, Vue, Solid, or Svelte, install the framework-specific package instead — it re-exports everything from @orpc/tanstack-query with the correct framework types. See the sidebar for the relevant page.

Setup

1

Create your oRPC client

import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { RouterClient } from '@orpc/server'
import type { router } from './server'

const link = new RPCLink({ url: 'http://localhost:3000/rpc' })
export const orpc: RouterClient<typeof router> = createORPCClient(link)
2

Create router utils

import { createRouterUtils } from '@orpc/tanstack-query'
import { orpc } from './client'

export const orpcUtils = createRouterUtils(orpc)
createRouterUtils (also exported as createTanstackQueryUtils) returns a proxy that mirrors your router’s shape. At each procedure you get queryOptions, infiniteOptions, mutationOptions, queryKey, infiniteKey, mutationKey, and call.

Query options

queryOptions() generates the queryKey and queryFn needed by useQuery, useSuspenseQuery, prefetchQuery, and similar hooks:
const options = orpcUtils.planet.list.queryOptions({ input: { limit: 10 } })
// options.queryKey — typed DataTag for cache operations
// options.queryFn  — calls orpc.planet.list({ limit: 10 })
Pass these directly to useQuery:
const { data } = useQuery(orpcUtils.planet.list.queryOptions({ input: { limit: 10 } }))

Conditional fetching with skipToken

import { skipToken } from '@tanstack/query-core'

const { data } = useQuery(
  orpcUtils.planet.find.queryOptions({
    input: isReady ? { id: planetId } : skipToken,
  }),
)
// enabled is automatically set to false when skipToken is used as input

Infinite query options

infiniteOptions() generates options for useInfiniteQuery:
const { data } = useInfiniteQuery(
  orpcUtils.planet.list.infiniteOptions({
    input: pageParam => ({ cursor: pageParam, limit: 20 }),
    initialPageParam: 0,
    getNextPageParam: lastPage => lastPage.nextCursor,
  }),
)

Mutation options

mutationOptions() generates options for useMutation:
const { mutate } = useMutation(
  orpcUtils.planet.create.mutationOptions({
    onSuccess: (data) => {
      console.log('Created:', data.name)
    },
  }),
)

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

Cache invalidation with queryKey

queryKey() generates a fully-typed cache key for targeted invalidation:
const queryClient = useQueryClient()

// Invalidate all planet.list queries
await queryClient.invalidateQueries({ queryKey: orpcUtils.planet.list.queryKey() })

// Invalidate a specific planet.list query
await queryClient.invalidateQueries({
  queryKey: orpcUtils.planet.list.queryKey({ input: { limit: 10 } }),
})
Use the .key() method (available at any router level) for partial matching — it matches all queries under that path:
// Invalidate all planet queries (list, find, create, ...)
await queryClient.invalidateQueries({
  queryKey: orpcUtils.planet.key(),
})

// Invalidate all queries in the entire router
await queryClient.invalidateQueries({
  queryKey: orpcUtils.key(),
})

Mutation key

const isMutating = useIsMutating({
  mutationKey: orpcUtils.planet.create.mutationKey(),
})

Calling the procedure directly

Each procedure utils exposes a call property which is the raw client function:
const planets = await orpcUtils.planet.list.call({ limit: 10 })

Streaming queries (SSE / Event Iterator)

oRPC supports server-sent events through event iterators. The experimental_streamedOptions and experimental_liveOptions helpers integrate them with TanStack Query. See Streaming & SSE for how to define streaming procedures on the server.

Streamed queries

A streamed query accumulates chunks into an array as they arrive:
const { data } = useQuery(
  orpcUtils.planet.stream.experimental_streamedOptions({ input: {} }),
)
// data is the accumulated array of chunks

Live queries

A live query replaces the entire result with each new chunk — useful for real-time data where you only want the latest value:
const { data } = useQuery(
  orpcUtils.planet.live.experimental_liveOptions({ input: {} }),
)
// data is always the latest chunk

Default options

Pass experimental_defaults to createRouterUtils to set default options for all procedures:
const orpcUtils = createRouterUtils(orpc, {
  experimental_defaults: {
    planet: {
      list: {
        queryOptions: { staleTime: 60_000 },
      },
    },
  },
})

Build docs developers (and LLMs) love