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.
Prerequisites
- 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
Understand Firestore's search limitations
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.
1// What Firestore CAN do: prefix/starts-with search2import { collection, query, where, orderBy, getDocs } from 'firebase/firestore'34async function prefixSearch(field: string, prefix: string) {5 // Matches documents where field starts with prefix6 const q = query(7 collection(db, 'products'),8 where(field, '>=', prefix),9 where(field, '<=', prefix + '\uf8ff'),10 orderBy(field),11 )12 const snapshot = await getDocs(q)13 return snapshot.docs.map((d) => ({ id: d.id, ...d.data() }))14}1516// prefixSearch('name', 'Mac') → 'MacBook Pro', 'Mac Mini'17// 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.
Set up Algolia for full-text search
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.
1// Install: npm install algoliasearch2import algoliasearch from 'algoliasearch'34// Initialize Algolia client (client-side uses Search-Only API Key)5const algoliaClient = algoliasearch(6 process.env.NEXT_PUBLIC_ALGOLIA_APP_ID!,7 process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY! // Search-only key, safe for client8)910const index = algoliaClient.initIndex('products')1112// Search products13async function searchProducts(queryText: string) {14 const { hits } = await index.search(queryText, {15 hitsPerPage: 20,16 attributesToRetrieve: ['name', 'description', 'price', 'objectID'],17 })18 return hits19}2021// searchProducts('wireless headphones') → finds 'Sony Wireless Headphones',22// 'Bose QuietComfort Headphones', etc.Expected result: Algolia returns search results with typo tolerance, ranking, and highlighting in under 50ms.
Sync Firestore data to Algolia with a Cloud Function
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.
1// functions/src/index.ts2import { onDocumentCreated, onDocumentUpdated, onDocumentDeleted } from 'firebase-functions/v2/firestore'3import { defineSecret } from 'firebase-functions/params'4import algoliasearch from 'algoliasearch'56const algoliaAppId = defineSecret('ALGOLIA_APP_ID')7const algoliaAdminKey = defineSecret('ALGOLIA_ADMIN_KEY')89function getIndex() {10 const client = algoliasearch(algoliaAppId.value(), algoliaAdminKey.value())11 return client.initIndex('products')12}1314export const onProductCreated = onDocumentCreated(15 { document: 'products/{productId}', secrets: [algoliaAppId, algoliaAdminKey] },16 async (event) => {17 const data = event.data?.data()18 if (!data) return19 const index = getIndex()20 await index.saveObject({ objectID: event.params.productId, ...data })21 }22)2324export const onProductUpdated = onDocumentUpdated(25 { document: 'products/{productId}', secrets: [algoliaAppId, algoliaAdminKey] },26 async (event) => {27 const data = event.data?.after.data()28 if (!data) return29 const index = getIndex()30 await index.saveObject({ objectID: event.params.productId, ...data })31 }32)3334export const onProductDeleted = onDocumentDeleted(35 { document: 'products/{productId}', secrets: [algoliaAppId, algoliaAdminKey] },36 async (event) => {37 const index = getIndex()38 await index.deleteObject(event.params.productId)39 }40)Expected result: Every Firestore write automatically syncs to Algolia, keeping the search index up to date.
Consider Typesense as an open-source alternative
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.
1// Install: npm install typesense2import Typesense from 'typesense'34const typesenseClient = new Typesense.Client({5 nodes: [{6 host: 'your-typesense-host.a1.typesense.net',7 port: 443,8 protocol: 'https',9 }],10 apiKey: process.env.NEXT_PUBLIC_TYPESENSE_SEARCH_KEY!,11})1213async function searchProducts(queryText: string) {14 const results = await typesenseClient15 .collections('products')16 .documents()17 .search({18 q: queryText,19 query_by: 'name,description',20 per_page: 20,21 })22 return results.hits?.map((hit: any) => hit.document) ?? []23}2425// Firebase Extensions: install 'Search with Typesense'26// from the Firebase Console to auto-sync Firestore → TypesenseExpected result: Full-text search works through Typesense with automatic Firestore sync via the Firebase extension.
Build a search UI component
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.
1import { useState, useEffect } from 'react'23function SearchBar() {4 const [query, setQuery] = useState('')5 const [results, setResults] = useState<any[]>([])6 const [loading, setLoading] = useState(false)78 useEffect(() => {9 if (!query.trim()) { setResults([]); return }1011 const timer = setTimeout(async () => {12 setLoading(true)13 const hits = await searchProducts(query)14 setResults(hits)15 setLoading(false)16 }, 300) // 300ms debounce1718 return () => clearTimeout(timer)19 }, [query])2021 return (22 <div>23 <input24 type="text"25 value={query}26 onChange={(e) => setQuery(e.target.value)}27 placeholder="Search products..."28 />29 {loading && <p>Searching...</p>}30 {results.map((hit) => (31 <div key={hit.objectID}>32 <h3>{hit.name}</h3>33 <p>{hit.description}</p>34 </div>35 ))}36 </div>37 )38}Expected result: A responsive search UI that shows results as the user types, powered by the external search service.
Complete working example
1import algoliasearch from 'algoliasearch'23const client = algoliasearch(4 process.env.NEXT_PUBLIC_ALGOLIA_APP_ID!,5 process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY!6)78const productsIndex = client.initIndex('products')910export interface SearchResult {11 objectID: string12 name: string13 description: string14 price: number15 category: string16}1718export async function searchProducts(19 queryText: string,20 options?: { category?: string; page?: number; hitsPerPage?: number }21): Promise<{ hits: SearchResult[]; totalHits: number; totalPages: number }> {22 const filters = options?.category23 ? `category:${options.category}`24 : ''2526 const { hits, nbHits, nbPages } = await productsIndex.search<SearchResult>(27 queryText,28 {29 hitsPerPage: options?.hitsPerPage ?? 20,30 page: options?.page ?? 0,31 filters,32 attributesToRetrieve: ['name', 'description', 'price', 'category'],33 attributesToHighlight: ['name', 'description'],34 }35 )3637 return {38 hits,39 totalHits: nbHits,40 totalPages: nbPages,41 }42}4344export async function prefixSearch(45 db: any,46 collectionName: string,47 field: string,48 prefix: string49) {50 const { collection, query, where, orderBy, limit, getDocs } = await import('firebase/firestore')51 const q = query(52 collection(db, collectionName),53 where(field, '>=', prefix),54 where(field, '<=', prefix + '\uf8ff'),55 orderBy(field),56 limit(10)57 )58 const snap = await getDocs(q)59 return snap.docs.map((d) => ({ id: d.id, ...d.data() }))60}Common mistakes when performing Full-Text Search in Firebase
Why it's a problem: Assuming Firestore can search for words inside a text field like SQL LIKE '%keyword%'
How to avoid: Firestore has no contains or LIKE operator. Use a dedicated search service like Algolia or Typesense for full-text search.
Why it's a problem: Storing the Algolia Admin API Key in client-side code, exposing write access to the search index
How to avoid: Only use the Search-Only API Key on the client. The Admin Key belongs in Cloud Functions, stored as a Secret.
Why it's a problem: Not handling the initial backfill — only new documents get synced to the search index
How to avoid: 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
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to add full-text search to my Firebase app. Firestore does not have native search. Show me how to integrate Algolia with Cloud Functions v2 that sync Firestore documents to an Algolia index on create, update, and delete. Include the client-side search query code.
Add a search bar that searches products by name and description. Since Firestore does not support full-text search, integrate Algolia. Create Cloud Functions to sync the products collection to an Algolia index, and use algoliasearch on the client to show results with debounced input.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation