FlutterFlow's built-in Firestore search only supports prefix matching. For real full-text search with facets, autocomplete, and typo tolerance, integrate Algolia or Typesense via the API Manager. Sync only the fields users need to search — never sync entire documents including PII — and call the search API from a Custom Action with debouncing to keep your quota usage low.
Why Firestore Search Is Not Enough
Firestore queries use index-based equality and range filters — they cannot do full-text search, typo tolerance, or ranking by relevance. Users expect Google-quality search in every app. Algolia and Typesense are purpose-built search engines that solve this. They store a copy of your searchable data (the 'index') and return results in milliseconds with relevance scoring, typo correction, highlighted match snippets, and faceted filtering. The integration path in FlutterFlow is: Firestore triggers a Cloud Function on document writes → Cloud Function syncs the record to Algolia/Typesense → app calls search API via Custom Action → results displayed in a ListView.
Prerequisites
- A FlutterFlow project with Firestore connected and at least one collection to search
- An Algolia account (free tier: 10,000 records, 10,000 searches/month) or a Typesense Cloud account
- Firebase Cloud Functions enabled on your Firebase project (Blaze plan required)
- FlutterFlow Pro or higher for Custom Actions and code export
Step-by-step guide
Configure your Algolia index and define searchable fields
Configure your Algolia index and define searchable fields
Log in to your Algolia dashboard at dashboard.algolia.com. Create a new Application, then create an Index named after your Firestore collection (e.g., 'products'). In the Index Settings, go to 'Searchable Attributes' and list only the fields users should search: title, description, category, tags. Do NOT include email, phone, userId, paymentInfo, or any PII. Under 'Attributes for Faceting', add: category, brand, priceRange, inStock. Under 'Ranking', move your most important attribute (e.g., 'popularity') to the top of the custom ranking. Copy your Application ID, Search-Only API Key (for client use), and Admin API Key (for Cloud Functions only — never expose this in the app).
Expected result: Your Algolia index is configured with correct searchable attributes and facets visible in the Algolia dashboard.
Create a Cloud Function to sync Firestore to Algolia
Create a Cloud Function to sync Firestore to Algolia
In your Firebase project, open the Functions directory in a code editor. Install the algoliasearch package: npm install algoliasearch. Create a new file syncToAlgolia.js. Write three exported functions: onProductCreate (triggers on Firestore document create), onProductUpdate (triggers on update), and onProductDelete (triggers on delete). Each function should initialize the Algolia client using environment variables ALGOLIA_APP_ID and ALGOLIA_ADMIN_KEY, then call index.saveObject for create/update or index.deleteObject for delete. Store only the safe searchable fields — not the entire Firestore document. Deploy with: firebase deploy --only functions. Set the environment variables with: firebase functions:config:set algolia.app_id='YOUR_ID' algolia.admin_key='YOUR_KEY'.
1// functions/syncToAlgolia.js2const functions = require('firebase-functions');3const algoliasearch = require('algoliasearch');45const client = algoliasearch(6 functions.config().algolia.app_id,7 functions.config().algolia.admin_key8);9const index = client.initIndex('products');1011// Sync on create12exports.onProductCreate = functions.firestore13 .document('products/{productId}')14 .onCreate(async (snap, context) => {15 const data = snap.data();16 await index.saveObject({17 objectID: context.params.productId,18 title: data.title,19 description: data.description,20 category: data.category,21 brand: data.brand,22 priceRange: data.priceRange,23 inStock: data.inStock,24 imageUrl: data.imageUrl,25 });26 });2728// Sync on update29exports.onProductUpdate = functions.firestore30 .document('products/{productId}')31 .onUpdate(async (change, context) => {32 const data = change.after.data();33 await index.partialUpdateObject({34 objectID: context.params.productId,35 title: data.title,36 description: data.description,37 category: data.category,38 brand: data.brand,39 priceRange: data.priceRange,40 inStock: data.inStock,41 });42 });4344// Remove on delete45exports.onProductDelete = functions.firestore46 .document('products/{productId}')47 .onDelete(async (snap, context) => {48 await index.deleteObject(context.params.productId);49 });Expected result: Cloud Functions deploy successfully. Creating or updating a product in Firestore causes a corresponding record to appear in your Algolia index within 2-3 seconds.
Build the search UI with autocomplete TextField
Build the search UI with autocomplete TextField
In FlutterFlow, create a new Search page. Add a TextField widget at the top with placeholder 'Search products...'. Create a Page State variable 'searchQuery' (String) and a 'searchResults' variable (JSON or a List of your result type). Bind the TextField's value to searchQuery. Add an onChange Action Flow that updates searchQuery from the field value, then calls a Custom Action named 'searchAlgolia'. For autocomplete, also create a 'suggestions' Page State variable (String List) and display them in a small overlay ListView that appears below the search field when suggestions is not empty. Tapping a suggestion sets the searchQuery TextField to that value and triggers a full search.
Expected result: Typing in the search field triggers a visual loading indicator after 500ms, then populates the results list with matched items.
Implement faceted filter checkboxes
Implement faceted filter checkboxes
Below the search bar, add a horizontal ScrollView containing filter chips or checkboxes for each facet (category, brand, priceRange, inStock). Create Page State variables for each active filter: 'selectedCategories' (String List), 'selectedBrands' (String List), 'inStockOnly' (Boolean). When any filter changes, call the searchAlgolia Custom Action again with the updated filter parameters. In the Custom Action, pass facetFilters to the Algolia search call: e.g., facetFilters: [['category:Electronics', 'category:Clothing'], 'inStock:true']. The array-of-arrays syntax means OR within the inner array and AND between outer elements.
Expected result: Selecting category 'Electronics' updates the results list to show only electronics. Combining category + in-stock filters correctly applies both constraints.
Display highlighted result snippets
Display highlighted result snippets
Algolia returns a '_highlightResult' object with each search hit, containing HTML-marked versions of matching text (e.g., 'Wireless <em>headphone</em>s'). In your Custom Action, extract the highlight snippet for the title and description fields. Since FlutterFlow's Text widget does not support HTML by default, use a Custom Widget that renders the highlighted text using Flutter's Text.rich() with a TextSpan that bolds or colors the matched portion. Alternatively, strip the HTML tags and use a simple bold style on the matched substring using a Custom Function named 'parseHighlight'.
1// Custom Function: parseHighlight2// Strips Algolia <em> tags and returns plain text with match position3Map<String, dynamic> parseHighlight(String highlightedText) {4 final RegExp emTag = RegExp(r'<\/?(em)>');5 final plainText = highlightedText.replaceAll(emTag, '');6 final matchStart = highlightedText.indexOf('<em>');7 int matchEnd = -1;8 if (matchStart >= 0) {9 final afterStart = highlightedText.substring(matchStart + 4);10 final endTag = afterStart.indexOf('</em>');11 matchEnd = matchStart + (endTag >= 0 ? endTag : 0);12 }13 return {14 'text': plainText,15 'matchStart': matchStart,16 'matchEnd': matchEnd,17 };18}Expected result: Search results display the matched portion of each title in bold or a different color, making it immediately clear why each result matches.
Log search analytics to Firestore
Log search analytics to Firestore
At the end of your searchAlgolia Custom Action, after receiving results, write a document to a Firestore 'searchLogs' collection. Include: query (String), resultCount (Integer), filters applied (Map), userId (String, from currentUser), timestamp (DateTime), and queryId (String, from Algolia's queryID field if you enable clickAnalytics). This data powers a simple analytics dashboard showing top searches, zero-result queries, and filter usage patterns. Review zero-result queries weekly to find content gaps or synonym opportunities in your Algolia index configuration.
Expected result: After each search, a new document appears in Firestore's searchLogs collection with the query, result count, and timestamp.
Complete working example
1// Custom Action: searchAlgolia2// Add to FlutterFlow Custom Code panel3// Requires: http package (add to dependencies)45import 'dart:convert';6import 'package:http/http.dart' as http;78Future<List<dynamic>> searchAlgolia({9 required String appId,10 required String searchApiKey,11 required String indexName,12 required String query,13 List<String> selectedCategories = const [],14 List<String> selectedBrands = const [],15 bool inStockOnly = false,16 int hitsPerPage = 20,17}) async {18 // Build facet filters19 final List<dynamic> facetFilters = [];20 if (selectedCategories.isNotEmpty) {21 facetFilters.add(22 selectedCategories.map((c) => 'category:$c').toList(),23 );24 }25 if (selectedBrands.isNotEmpty) {26 facetFilters.add(27 selectedBrands.map((b) => 'brand:$b').toList(),28 );29 }30 if (inStockOnly) {31 facetFilters.add('inStock:true');32 }3334 final body = jsonEncode({35 'query': query,36 'hitsPerPage': hitsPerPage,37 'attributesToHighlight': ['title', 'description'],38 'clickAnalytics': true,39 if (facetFilters.isNotEmpty) 'facetFilters': facetFilters,40 });4142 final response = await http.post(43 Uri.parse(44 'https://$appId-dsn.algolia.net/1/indexes/$indexName/query',45 ),46 headers: {47 'X-Algolia-Application-Id': appId,48 'X-Algolia-API-Key': searchApiKey,49 'Content-Type': 'application/json',50 },51 body: body,52 );5354 if (response.statusCode == 200) {55 final data = jsonDecode(response.body) as Map<String, dynamic>;56 return data['hits'] as List<dynamic>;57 } else {58 throw Exception('Algolia search failed: ${response.statusCode}');59 }60}Common mistakes when implementing a Custom Search Engine in FlutterFlow
Why it's a problem: Syncing entire Firestore documents to Algolia including user PII
How to avoid: In your Cloud Function sync logic, explicitly list only the fields that users need to search: title, description, category, imageUrl. Never use spread syntax to copy the entire document object.
Why it's a problem: Calling the Algolia API directly from Firestore Security Rules or client-side without debouncing
How to avoid: Implement a 400-600ms debounce in your Custom Action using a timer that resets on each new call. Only fire the API request after the user stops typing for the debounce duration.
Why it's a problem: Building a manual full-text search using Firestore's array-contains on a tokenized words field
How to avoid: Use a dedicated search engine like Algolia or Typesense. Their free tiers handle typical small app volumes well within limits.
Best practices
- Keep your Algolia index schema lean — only sync fields that are actually displayed in search results or used as filters.
- Set up index synonyms in Algolia for your domain vocabulary (e.g., 'TV' = 'television') so users find results regardless of phrasing.
- Monitor your 'zero results' search queries weekly in Algolia's Analytics dashboard and add synonyms or new content to fill those gaps.
- Always use the Search-Only API Key in your FlutterFlow app — never the Admin API Key. Store the Admin Key only in Cloud Functions environment variables.
- Enable Algolia's Click Analytics and use the queryID to track which results users actually engage with — feed this data back into ranking.
- Paginate search results using Algolia's page and hitsPerPage parameters rather than loading all results at once.
- Test your search relevance with real queries from actual users, not synthetic test data — real usage patterns often reveal surprising ranking issues.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm integrating Algolia search into a FlutterFlow app. My Firestore collection is 'products' with fields: title, description, category, brand, priceRange, inStock, imageUrl, sellerId, sellerEmail. Which fields should I sync to Algolia and why? Also explain how to write a Firebase Cloud Function that triggers on Firestore document create/update/delete and syncs only the safe fields.
I have a FlutterFlow Custom Action that calls Algolia's search API using the http package. The action receives a query string and a list of selected category filters. Write the complete Dart function body that builds the Algolia facetFilters array, makes the POST request to the search API, and returns the list of hit objects so FlutterFlow can bind them to a ListView.
Frequently asked questions
Should I use Algolia or Typesense for my FlutterFlow app?
Algolia is easier to set up and has more FlutterFlow community examples. Typesense is open-source, cheaper at scale, and can be self-hosted for full data control. For most apps starting out, Algolia's free tier (10K records, 10K searches/month) is sufficient. Choose Typesense if data privacy or cost at scale is a priority.
Can I search across multiple Firestore collections at once?
Yes. Create separate Algolia indexes for each collection (e.g., 'products', 'articles', 'users'). Use Algolia's multi-index search API endpoint to query several indexes in a single HTTP request. In your Custom Action, merge and sort the results by relevance score before displaying them.
How do I handle Algolia search results in FlutterFlow's data binding system?
Algolia returns JSON. In your Custom Action, parse the response into a List of Maps (List<dynamic>). Store this in a Page State variable of type JSON or create a custom data type in FlutterFlow that mirrors the Algolia hit structure. Bind your ListView to this variable and reference individual fields using FlutterFlow's JSON path syntax.
What happens if the Algolia index gets out of sync with Firestore?
This can happen if a Cloud Function fails to execute (e.g., cold start timeout, function crash). To recover, write a one-time Cloud Function that reads all Firestore documents and re-indexes them into Algolia using batch save (index.saveObjects). Run this manually from the Firebase console when you notice discrepancies.
Is there a way to search without Cloud Functions, just from FlutterFlow directly?
Yes — you can call the Algolia Search API directly from a Custom Action using the http package. You still need to sync data to Algolia first, which requires Cloud Functions for real-time sync. The search call itself can be made client-side using only the Search-Only API Key.
How do I prevent users from seeing other users' private content in search results?
Use Algolia's optional filters or security rules per user. The recommended approach is to add a 'visibility' field to every searchable document ('public' or 'private') and always include filters: 'visibility:public' in your search calls for unauthenticated or cross-user searches. For authenticated users viewing their own private content, add a second filter for their userId.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation