# How to Perform Full-Text Search in Firebase

- Tool: Firebase
- Difficulty: Intermediate
- Time required: 15-20 min
- Compatibility: Firebase JS SDK v9+, Blaze plan (for Cloud Functions)
- Last updated: March 2026

## TL;DR

Firestore has no built-in full-text search. Prefix matching with >= and <= operators only works for starts-with queries. For real full-text search you need an external service like Algolia, Typesense, or Meilisearch. The typical pattern is: write data to Firestore, use a Cloud Function trigger to sync it to the search service, and query the search service from your client. Firebase Data Connect (PostgreSQL-based) now supports native full-text search as an alternative for new projects.

## Implementing Full-Text Search with Firebase

Firestore is built for fast reads on structured queries, not for searching inside text content. It has no LIKE operator, no trigram indexes, and no way to find a word in the middle of a string. This tutorial starts with what Firestore can do natively (prefix matching), then walks through integrating a dedicated search service like Algolia or Typesense using Cloud Functions to keep the search index in sync with your Firestore data.

## Before you start

- A Firebase project on the Blaze plan (required for Cloud Functions)
- Firebase JS SDK v9+ installed in your project
- An Algolia or Typesense account (free tiers available)
- Basic understanding of Cloud Functions triggers

## Step-by-step guide

### 1. Understand Firestore's search limitations

Firestore queries only support exact matches, range comparisons, and array membership. There is no contains, LIKE, or regex operator. The only text search you can do natively is prefix matching: find documents where a field starts with a given string. This is useful for autocomplete on names but inadequate for searching within descriptions, blog posts, or any longer text content.

```
// What Firestore CAN do: prefix/starts-with search
import { collection, query, where, orderBy, getDocs } from 'firebase/firestore'

async function prefixSearch(field: string, prefix: string) {
  // Matches documents where field starts with prefix
  const q = query(
    collection(db, 'products'),
    where(field, '>=', prefix),
    where(field, '<=', prefix + '\uf8ff'),
    orderBy(field),
  )
  const snapshot = await getDocs(q)
  return snapshot.docs.map((d) => ({ id: d.id, ...d.data() }))
}

// prefixSearch('name', 'Mac') → 'MacBook Pro', 'Mac Mini'
// But searching for 'Pro' will NOT find 'MacBook Pro'
```

**Expected result:** Prefix search returns documents where the field starts with the given string. Mid-string and multi-word searches return no results.

### 2. Set up Algolia for full-text search

Algolia is the most commonly used search service with Firebase. Create an Algolia account, get your Application ID and Admin API Key, and install the algoliasearch package. Each Algolia index mirrors a Firestore collection — when you add or update a document in Firestore, a Cloud Function syncs it to Algolia. Client searches go directly to Algolia for instant results.

```
// Install: npm install algoliasearch
import algoliasearch from 'algoliasearch'

// Initialize Algolia client (client-side uses Search-Only API Key)
const algoliaClient = algoliasearch(
  process.env.NEXT_PUBLIC_ALGOLIA_APP_ID!,
  process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY!  // Search-only key, safe for client
)

const index = algoliaClient.initIndex('products')

// Search products
async function searchProducts(queryText: string) {
  const { hits } = await index.search(queryText, {
    hitsPerPage: 20,
    attributesToRetrieve: ['name', 'description', 'price', 'objectID'],
  })
  return hits
}

// searchProducts('wireless headphones') → finds 'Sony Wireless Headphones',
// 'Bose QuietComfort Headphones', etc.
```

**Expected result:** Algolia returns search results with typo tolerance, ranking, and highlighting in under 50ms.

### 3. Sync Firestore data to Algolia with a Cloud Function

Write Cloud Functions that trigger on Firestore document creates, updates, and deletes. Each function syncs the change to the Algolia index. Set the Algolia objectID to the Firestore document ID so updates and deletes target the correct record. Store the Algolia Admin API Key as a Cloud Secret, never in code.

```
// functions/src/index.ts
import { onDocumentCreated, onDocumentUpdated, onDocumentDeleted } from 'firebase-functions/v2/firestore'
import { defineSecret } from 'firebase-functions/params'
import algoliasearch from 'algoliasearch'

const algoliaAppId = defineSecret('ALGOLIA_APP_ID')
const algoliaAdminKey = defineSecret('ALGOLIA_ADMIN_KEY')

function getIndex() {
  const client = algoliasearch(algoliaAppId.value(), algoliaAdminKey.value())
  return client.initIndex('products')
}

export const onProductCreated = onDocumentCreated(
  { document: 'products/{productId}', secrets: [algoliaAppId, algoliaAdminKey] },
  async (event) => {
    const data = event.data?.data()
    if (!data) return
    const index = getIndex()
    await index.saveObject({ objectID: event.params.productId, ...data })
  }
)

export const onProductUpdated = onDocumentUpdated(
  { document: 'products/{productId}', secrets: [algoliaAppId, algoliaAdminKey] },
  async (event) => {
    const data = event.data?.after.data()
    if (!data) return
    const index = getIndex()
    await index.saveObject({ objectID: event.params.productId, ...data })
  }
)

export const onProductDeleted = onDocumentDeleted(
  { document: 'products/{productId}', secrets: [algoliaAppId, algoliaAdminKey] },
  async (event) => {
    const index = getIndex()
    await index.deleteObject(event.params.productId)
  }
)
```

**Expected result:** Every Firestore write automatically syncs to Algolia, keeping the search index up to date.

### 4. Consider Typesense as an open-source alternative

Typesense is a free, open-source search engine that can be self-hosted or used as a managed service. It offers similar features to Algolia (typo tolerance, faceting, geosearch) with predictable pricing. Firebase has an official Typesense extension that handles the Firestore-to-Typesense sync automatically without writing Cloud Functions.

```
// Install: npm install typesense
import Typesense from 'typesense'

const typesenseClient = new Typesense.Client({
  nodes: [{
    host: 'your-typesense-host.a1.typesense.net',
    port: 443,
    protocol: 'https',
  }],
  apiKey: process.env.NEXT_PUBLIC_TYPESENSE_SEARCH_KEY!,
})

async function searchProducts(queryText: string) {
  const results = await typesenseClient
    .collections('products')
    .documents()
    .search({
      q: queryText,
      query_by: 'name,description',
      per_page: 20,
    })
  return results.hits?.map((hit: any) => hit.document) ?? []
}

// Firebase Extensions: install 'Search with Typesense'
// from the Firebase Console to auto-sync Firestore → Typesense
```

**Expected result:** Full-text search works through Typesense with automatic Firestore sync via the Firebase extension.

### 5. Build a search UI component

Create a search input that queries the search service on every keystroke (debounced) and displays results. The search results include the Firestore document ID as objectID, which you can use to link back to the full document in your app or fetch additional data from Firestore if needed.

```
import { useState, useEffect } from 'react'

function SearchBar() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState<any[]>([])
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    if (!query.trim()) { setResults([]); return }

    const timer = setTimeout(async () => {
      setLoading(true)
      const hits = await searchProducts(query)
      setResults(hits)
      setLoading(false)
    }, 300) // 300ms debounce

    return () => clearTimeout(timer)
  }, [query])

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search products..."
      />
      {loading && <p>Searching...</p>}
      {results.map((hit) => (
        <div key={hit.objectID}>
          <h3>{hit.name}</h3>
          <p>{hit.description}</p>
        </div>
      ))}
    </div>
  )
}
```

**Expected result:** A responsive search UI that shows results as the user types, powered by the external search service.

## Complete code example

File: `search-service.ts`

```typescript
import algoliasearch from 'algoliasearch'

const client = algoliasearch(
  process.env.NEXT_PUBLIC_ALGOLIA_APP_ID!,
  process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY!
)

const productsIndex = client.initIndex('products')

export interface SearchResult {
  objectID: string
  name: string
  description: string
  price: number
  category: string
}

export async function searchProducts(
  queryText: string,
  options?: { category?: string; page?: number; hitsPerPage?: number }
): Promise<{ hits: SearchResult[]; totalHits: number; totalPages: number }> {
  const filters = options?.category
    ? `category:${options.category}`
    : ''

  const { hits, nbHits, nbPages } = await productsIndex.search<SearchResult>(
    queryText,
    {
      hitsPerPage: options?.hitsPerPage ?? 20,
      page: options?.page ?? 0,
      filters,
      attributesToRetrieve: ['name', 'description', 'price', 'category'],
      attributesToHighlight: ['name', 'description'],
    }
  )

  return {
    hits,
    totalHits: nbHits,
    totalPages: nbPages,
  }
}

export async function prefixSearch(
  db: any,
  collectionName: string,
  field: string,
  prefix: string
) {
  const { collection, query, where, orderBy, limit, getDocs } = await import('firebase/firestore')
  const q = query(
    collection(db, collectionName),
    where(field, '>=', prefix),
    where(field, '<=', prefix + '\uf8ff'),
    orderBy(field),
    limit(10)
  )
  const snap = await getDocs(q)
  return snap.docs.map((d) => ({ id: d.id, ...d.data() }))
}
```

## Common mistakes

- **Assuming Firestore can search for words inside a text field like SQL LIKE '%keyword%'** — undefined Fix: Firestore has no contains or LIKE operator. Use a dedicated search service like Algolia or Typesense for full-text search.
- **Storing the Algolia Admin API Key in client-side code, exposing write access to the search index** — undefined Fix: Only use the Search-Only API Key on the client. The Admin Key belongs in Cloud Functions, stored as a Secret.
- **Not handling the initial backfill — only new documents get synced to the search index** — undefined Fix: Write a one-time migration script that reads all existing Firestore documents and batch-imports them into the search service before enabling the Cloud Function triggers.

## Best practices

- Use Firestore prefix search only for simple autocomplete on short fields like names or titles
- Choose Algolia for managed search with enterprise features, or Typesense for open-source with predictable pricing
- Store search API keys as Cloud Secrets using defineSecret, never hardcode them
- Debounce search input by 200-300ms to reduce API calls and costs
- Only index the fields you need to search — avoid sending entire documents to the search service
- Use the Firebase Typesense extension for automatic sync without writing custom Cloud Functions
- Consider Firebase Data Connect (PostgreSQL-based) for new projects that need native full-text search

## Frequently asked questions

### Why does Firestore not support full-text search?

Firestore is designed for fast indexed lookups on structured data. Full-text search requires inverted indexes, tokenization, stemming, and ranking — features that would add complexity and cost to every Firestore instance. Google recommends using a dedicated search service.

### Is Algolia free to use with Firebase?

Algolia has a free tier with 10,000 search requests per month and 10,000 records. For most small to medium apps, this is sufficient. Beyond that, pricing is based on search requests and records stored.

### Can I use Firebase Data Connect for full-text search instead?

Yes. Firebase Data Connect uses Cloud SQL PostgreSQL, which supports native full-text search with tsvector and tsquery. This is a good option for new projects, but requires migrating away from Firestore for the searchable data.

### How fast is the sync between Firestore and the search index?

Cloud Function triggers typically fire within 1-3 seconds of a Firestore write. Including the time to update the search index, new data is usually searchable within 2-5 seconds.

### Can I search across multiple Firestore collections?

Yes. Create a single search index that includes documents from multiple collections, each with a type field to distinguish them. Your Cloud Functions can sync multiple collections to the same Algolia index.

### Can RapidDev help integrate search into my Firebase app?

Yes. RapidDev can set up Algolia or Typesense integration with your Firebase project, including Cloud Function sync, search UI components, and faceted filtering tailored to your data model.

---

Source: https://www.rapidevelopers.com/firebase-tutorial/how-to-perform-full-text-search-in-firebase
© RapidDev — https://www.rapidevelopers.com/firebase-tutorial/how-to-perform-full-text-search-in-firebase
