---
title: "Queries"
description: "Reactive Convex data in Vue — useQuery for a single live subscription, useQueries for a set that changes at runtime, and the result-union variant."
canonical_url: "https://nuxt-convex-module.dev/guide/queries"
---
# Queries

> Reactive Convex data in Vue — useQuery for a single live subscription, useQueries for a set that changes at runtime, and the result-union variant.

A Convex query is a live subscription, not a fetch: it resolves once and then keeps updating as the underlying data changes. `useQuery` is auto-imported, as is every composable in this guide — `useQuery`, `useQueries`, `useMutation`, `useAction`, `usePaginatedQuery`, `useAsyncQuery`, `useUpload`, `useUploadQueue`, and `useStorageUrl` also carry `useConvex*`-prefixed aliases for when a name clashes with something else in your app.

<callout icon="i-lucide-radio">

The demos on this page and throughout the guide are **live**. They run the real composables against a real Convex deployment over the WebSocket sync protocol — nothing is mocked or recorded. Open a page in a second window and watch both update.

</callout>

## `useQuery()`

Returns a `ComputedRef<T | undefined>` — `undefined` while loading — and **throws** query errors (caught by an `errorCaptured` boundary, mirroring React's render-throw). Args are reactive; pass `'skip'` to pause.

```vue [pages/messages.vue]
<script setup lang="ts">
import { api } from '#convex/api'

const messages = useQuery(api.messages.list, {})

const profile = useQuery(
  api.users.get,
  computed(() => userId.value ? { userId: userId.value } : 'skip'),
)
</script>
```

The classic Convex demo — `useQuery` streams the list live, `useMutation` writes. Open this page in a second window and watch messages appear in both:

<playground-chat>



</playground-chat>

```vue [components/Chat.vue]
<script setup lang="ts">
import { api } from '#convex/api'

const messages = useQuery(api.messages.list, {})
const send = useMutation(api.messages.send)

const body = ref('')
</script>

<template>
  <ul>
    <li v-for="message in messages" :key="message._id">
      <strong>{{ message.author }}</strong>: {{ message.body }}
    </li>
  </ul>
  <form @submit.prevent="send({ author: 'me', body }).then(() => (body = ''))">
    <input v-model="body" placeholder="Message">
    <button type="submit">Send</button>
  </form>
</template>
```

`messages` is a `ComputedRef` — `undefined` while the first result loads, then it updates reactively on every change from any client. No refresh, no polling, no cache invalidation.

### `useQuery_experimental()`

<callout icon="i-lucide-flask-conical">

Experimental — upstream marks it so, and it may change in a minor release. See [experimental symbols](https://nuxt-convex-module.dev/getting-started/introduction#experimental).

</callout>

Convex's result/error split — takes an options object (`{ query, args, throwOnError? }`) and returns a `{ status }` union instead of throwing:

```ts
const result = useQuery_experimental({ query: api.messages.list, args: {} })
// result.value: { status: 'pending' }
//             | { status: 'success', data }
//             | { status: 'error', error }
```

Pass `throwOnError: true` to opt back into throwing to `errorCaptured` — the `'error'` variant is then excluded from the type.

## `useQueries()`

`useQuery` handles one query. When the *set* of queries changes at runtime — a dashboard with toggleable panels, a list of per-item subscriptions — you can't conditionally call composables. `useQueries` takes the whole set as one reactive object instead:

```ts
// Pass a getter (or ref/computed) to make the whole map reactive — per-query
// args are plain values, not refs.
const results = useQueries(() => ({
  messages: { query: api.messages.list, args: {} },
  profile: { query: api.users.get, args: { userId: userId.value } },
}))
```

Toggle the checkboxes to subscribe and unsubscribe live:

<playground-queries>



</playground-queries>

```vue [components/QueryPanel.vue]
<script setup lang="ts">
import type { RequestForQueries } from 'nuxt-convex-module/client'
import { api } from '#convex/api'

const watchMessages = ref(true)
const watchTasks = ref(false)

// The request object is built in a computed — adding or removing a key
// subscribes or unsubscribes that query on the fly.
const request = computed(() => {
  const queries: RequestForQueries = {}
  if (watchMessages.value)
    queries.messageCount = { query: api.messages.count, args: {} }
  if (watchTasks.value)
    queries.taskStats = { query: api.tasks.stats, args: {} }
  return queries
})

const results = useQueries(request)
</script>

<template>
  <span v-if="results.messageCount === undefined">loading…</span>
  <span v-else-if="results.messageCount instanceof Error">
    {{ results.messageCount.message }}
  </span>
  <span v-else>{{ results.messageCount }} messages</span>
</template>
```

The return is a `ShallowRef` of a record with the same keys as the request: use `results.value[key]` in script, `results[key]` in templates. Each key is `undefined` while its query loads, and if a query throws, the `Error` is **returned as the value** for that key — not thrown — so one failing query never takes down the others. Check `instanceof Error` before using a result.


## Sitemap

See the full [sitemap](https://nuxt-convex-module.dev/sitemap.md) for all pages.
