Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Implement a Custom Search Engine in FlutterFlow

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.

What you'll learn

  • How to set up Algolia or Typesense and sync only safe, searchable Firestore fields
  • How to call the search API from a FlutterFlow Custom Action with debouncing
  • How to build faceted filter checkboxes and display highlighted result snippets
  • How to log search queries to Firestore for search analytics
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read40-50 minFlutterFlow Pro+ (code export required for Custom Actions)March 2026RapidDev Engineering Team
TL;DR

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

1

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.

2

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'.

syncToAlgolia.js
1// functions/syncToAlgolia.js
2const functions = require('firebase-functions');
3const algoliasearch = require('algoliasearch');
4
5const client = algoliasearch(
6 functions.config().algolia.app_id,
7 functions.config().algolia.admin_key
8);
9const index = client.initIndex('products');
10
11// Sync on create
12exports.onProductCreate = functions.firestore
13 .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 });
27
28// Sync on update
29exports.onProductUpdate = functions.firestore
30 .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 });
43
44// Remove on delete
45exports.onProductDelete = functions.firestore
46 .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.

3

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.

4

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.

5

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'.

parse_highlight.dart
1// Custom Function: parseHighlight
2// Strips Algolia <em> tags and returns plain text with match position
3Map<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.

6

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

search_algolia.dart
1// Custom Action: searchAlgolia
2// Add to FlutterFlow Custom Code panel
3// Requires: http package (add to dependencies)
4
5import 'dart:convert';
6import 'package:http/http.dart' as http;
7
8Future<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 filters
19 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 }
33
34 final body = jsonEncode({
35 'query': query,
36 'hitsPerPage': hitsPerPage,
37 'attributesToHighlight': ['title', 'description'],
38 'clickAnalytics': true,
39 if (facetFilters.isNotEmpty) 'facetFilters': facetFilters,
40 });
41
42 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 );
53
54 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.

ChatGPT Prompt

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.

FlutterFlow Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.