---
title: "Full-text search"
description: "A type-safe fluent builder over Convex's native full-text search, driven reactively by useSearch."
canonical_url: "https://nuxt-backend.dev/docs/data/search"
---
# Full-text search

> A type-safe fluent builder over Convex's native full-text search, driven reactively by useSearch.

A type-safe builder over Convex's native `searchIndex` — index names, search fields, and `eq` filters are checked against your schema. Everything below assumes a search index on the table:

```ts [backend/schema.ts]
import { defineSchema, defineTable } from 'convex/server'
import { v } from 'convex/values'

export default defineSchema({
  // …your other tables
  messages: defineTable({ text: v.string(), userId: v.string() })
    .searchIndex('search_text', { searchField: 'text', filterFields: ['userId'] }),
})
```

## Fluent `search(...)`

```ts
import { search } from 'nuxt-backend/search'

const results = await search(ctx, 'messages')
  .withSearchIndex('search_text')
  .search('text', term)
  .eq('userId', userId)
  .take(20)
```

`.eq` accepts only the index's `filterFields`. Finish with `.take(n)`, `.collect()` (every ranked match) or `.first()` (the top match, or `null`); a blank term short-circuits to `[]` / `null` without touching the database.

## `defineSearch(...)`

`defineSearch(query, { table, index, searchField, defaultLimit? })` turns that into a ready-made query taking `{ query, limit? }`, which the `useSearch` composable drives reactively.

```ts [backend/search.ts]
import { defineSearch } from 'nuxt-backend/search'
import { query } from './_generated/server'

export const searchMessages = defineSearch(query, {
  table: 'messages',
  index: 'search_text',
  searchField: 'text',
  defaultLimit: 20,
})
```

## Reactive `useSearch()`

Debounced, reactive full-text search. Pauses while the term is blank.

```vue
<script setup lang="ts">
import { api } from '#backend/api'

const term = ref('')
const { results, isLoading, error } = useSearch(api.search.searchMessages, {
  term,
  debounce: 200,
  args: { limit: 10 },
})
</script>

<template>
  <input v-model="term" placeholder="Search…">
  <p v-if="error">{{ error.message }}</p>
  <ul>
    <li v-for="r in results" :key="r._id">{{ r.text }}</li>
  </ul>
</template>
```

`useSearch(query, options)` takes any query with a `{ query: string }` argument — a `defineSearch` one, or your own over the builder — and returns `results` (`[]` while loading, while the term is blank and after a failed read), `isLoading` (a non-empty term is in flight with no result yet), `error` (why the search failed, as `{ code, data, message }`, or `null`) and `term` (the debounced term currently driving the query). Options: `term` (a value, ref or getter; blank, whitespace-only, `null` or `undefined` pauses it), `debounce` (ms, default `200`, also a ref or getter) and `args` — a value, ref, or getter merged with `{ query }` on every request: `{ limit }` for a `defineSearch` query, or the filter fields your own query declares. A failed search never throws; see [Composable conventions](https://nuxt-backend.dev/docs/reference/nuxt/conventions).

<page-keys>

- Try it live in the playground: [/playground/client/search](https://nuxt-backend.dev/playground/client/search)
- API reference: [/docs/reference/api/search](https://nuxt-backend.dev/docs/reference/api/search)
- Source: [https://github.com/qruto/nuxt-backend/blob/main/src/convex/integrations/search.ts](https://github.com/qruto/nuxt-backend/blob/main/src/convex/integrations/search.ts)

</page-keys>


## Sitemap

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