Skip to main content
@orpc/vue-query provides Vue-specific utilities for TanStack Query. It wraps @tanstack/vue-query types and handles Vue’s reactive refs automatically — inputs and context can be plain values or MaybeRefOrGetter.

Installation

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

Setup

1

Install the Vue Query plugin

import { VueQueryPlugin } from '@tanstack/vue-query'
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.use(VueQueryPlugin)
app.mount('#app')
2

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: '/rpc' })
export const orpc: RouterClient<typeof router> = createORPCClient(link)
3

Create Vue Query utils

import { createORPCVueQueryUtils } from '@orpc/vue-query'
import { orpc } from './client'

export const orpcUtils = createORPCVueQueryUtils(orpc)

useQuery

<script setup lang="ts">
import { useQuery } from '@tanstack/vue-query'
import { orpcUtils } from './orpc'

const { data, isLoading, error } = useQuery(
  orpcUtils.planet.list.queryOptions({ input: { limit: 10 } }),
)
</script>

<template>
  <div v-if="isLoading">Loading...</div>
  <div v-else-if="error">{{ error.message }}</div>
  <ul v-else>
    <li v-for="planet in data" :key="planet.id">{{ planet.name }}</li>
  </ul>
</template>

Reactive inputs

Because inputs can be MaybeRefOrGetter, you can pass reactive refs directly and the query will re-run when the ref changes:
<script setup lang="ts">
import { ref } from 'vue'
import { useQuery } from '@tanstack/vue-query'
import { orpcUtils } from './orpc'

const limit = ref(10)

const { data } = useQuery(
  orpcUtils.planet.list.queryOptions({ input: limit }),
)
// The query re-fetches automatically when `limit` changes
</script>

useInfiniteQuery

<script setup lang="ts">
import { useInfiniteQuery } from '@tanstack/vue-query'
import { orpcUtils } from './orpc'

const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
  orpcUtils.planet.list.infiniteOptions({
    input: pageParam => ({ cursor: pageParam, limit: 10 }),
    initialPageParam: 0,
    getNextPageParam: lastPage => lastPage.nextCursor,
  }),
)
</script>

useMutation

<script setup lang="ts">
import { useMutation, useQueryClient } from '@tanstack/vue-query'
import { orpcUtils } from './orpc'

const queryClient = useQueryClient()

const { mutate, isPending } = useMutation(
  orpcUtils.planet.create.mutationOptions({
    onSuccess: async () => {
      await queryClient.invalidateQueries({
        queryKey: orpcUtils.planet.list.key({ type: 'query' }),
      })
    },
  }),
)
</script>

<template>
  <button :disabled="isPending" @click="mutate({ name: 'Mars' })">
    Create planet
  </button>
</template>

Cache invalidation

Use the key() method from GeneralUtils to generate partial matching keys:
const queryClient = useQueryClient()

// All planet.list queries (all inputs)
await queryClient.invalidateQueries({
  queryKey: orpcUtils.planet.list.key({ type: 'query' }),
})

// All planet queries (all procedures under planet)
await queryClient.invalidateQueries({
  queryKey: orpcUtils.planet.key(),
})
@orpc/vue-query uses @tanstack/vue-query types. The queryOptions() return value includes a reactive queryKey (a ComputedRef) so that TanStack Query re-subscribes when inputs change.

Build docs developers (and LLMs) love