Outside Components ​
Vue Apollo's composables rely on Vue's injection system to find the Apollo client. Inside a component's <script setup> (or any code that runs in a component's effect scope) the client is found through inject(DefaultApolloClient) automatically.
Outside of that context, useQuery and friends have no way to resolve a client. provideApolloClient and provideApolloClients bridge that gap by stashing the client in module-level state for the duration of a callback.
When you need this ​
You need provideApolloClient when calling Vue Apollo composables from code that is not a component setup:
- Vue Router navigation guards (
beforeEach,beforeEnter). - Standalone scripts, utility functions, or workers.
- Test setup code that runs outside of
mount().
You generally do not need it for:
- Pinia stores. Call
useSomeStore()from inside a component setup. The store can then useuseQuery/useMutationdirectly because injection has already been resolved. - Composables called from inside a component. Injection works through nested composables.
- Watch callbacks inside components. The component's effect scope is still active.
The pattern ​
provideApolloClient(client) returns a function that runs your callback with the client available:
import { gql } from '@apollo/client'
import { provideApolloClient, useQuery } from '@vue/apollo-composable'
import { apolloClient } from './apollo'
const result = provideApolloClient(apolloClient)(() => {
return useQuery(gql`
query Me {
me { id name }
}
`)
})The client is bound for the duration of the inner function. Composables called inside that function resolve the client through module state instead of Vue injection.
Vue Router guards ​
The canonical use case: a route guard that prefetches data before navigation.
import { gql } from '@apollo/client'
import { provideApolloClient, useQuery } from '@vue/apollo-composable'
import { createRouter } from 'vue-router'
import { apolloClient } from './apollo'
const router = createRouter({
// ...routes
})
router.beforeEach(async (to) => {
if (to.meta.requiresAuth) {
const { current, onResult, onError } = provideApolloClient(apolloClient)(() => {
return useQuery(gql`
query CurrentUser {
me { id role }
}
`, {
fetchPolicy: 'cache-first',
})
})
// Wait for the query to complete
await new Promise<void>((resolve) => {
onResult(() => resolve())
onError(() => resolve())
})
if (!current.value.result?.me) {
return '/login'
}
}
})Cleanup matters
Guards run outside any component's lifecycle, so the query has no scope to clean up against. If you start a long-running query (especially with pollInterval or a non-default fetchPolicy), it will leak.
For one-shot lookups, prefer client.query() directly:
const { data } = await apolloClient.query({
query: CURRENT_USER,
fetchPolicy: 'cache-first',
})client.query does not subscribe and has nothing to leak. Reach for provideApolloClient + useQuery only when you need the reactive current ref, or when you specifically want the cache-update behavior.
Standalone helpers ​
For utility functions outside any Vue context (CLI tools, server scripts, workers):
import { gql } from '@apollo/client'
import { provideApolloClient, useQuery } from '@vue/apollo-composable'
import { apolloClient } from './apollo'
export function getUserOnce(id: string) {
return provideApolloClient(apolloClient)(() => {
const { onResult, onError } = useQuery(gql`
query User($id: ID!) {
user(id: $id) { id name }
}
`, {
variables: { id },
})
return new Promise<any>((resolve, reject) => {
onResult(data => resolve(data))
onError(reject)
})
})
}For most one-off queries, apolloClient.query(...) directly is simpler.
Multiple clients ​
provideApolloClients is the dictionary variant:
import { provideApolloClients } from '@vue/apollo-composable'
import { analyticsClient, mainClient } from './apollo'
provideApolloClients({
default: mainClient,
analytics: analyticsClient,
})(() => {
// useQuery, useMutation, etc. can resolve by clientId in here
})See Multiple Clients for the full pattern.
How it works ​
provideApolloClient writes the client into a module-level variable, runs your callback, then clears it. Vue Apollo's useApolloClient checks Vue's injection first; if there is no injection context (because you are not in a component), it falls back to the module-level variable.
This means:
- The client only lives for the duration of the callback.
- Nested calls work: if you call
provideApolloClientinsideprovideApolloClient, the inner call wins until its callback returns. - It is not thread-safe across concurrent async work, because module state is shared. Do not start two independent
provideApolloClientblocks in parallel; chain them or use a single block.
Next steps ​
- Multiple Clients for the named-clients pattern.
- SSR Overview for the SSR equivalent (cache extract/restore on the server).
useApolloClientreference for the resolution API.