FlutterFlow supports three search approaches: client-side filtering works for lists under 500 items, Firestore prefix search handles up to 5,000 items with indexed fields, and Algolia or Typesense enables full-text search for larger datasets. All three use a TextField with debounce wired to a filtered ListView. Never use Firestore where with isEqualTo for search — it only matches exact full values.
Pick the Right Search for Your Data Size
Search in FlutterFlow is not one-size-fits-all. A product catalog with 200 items needs a completely different approach than a user directory with 50,000 names. Choosing the wrong approach means either unnecessarily complex infrastructure for a small dataset, or a broken search experience for a large one. This guide walks through all three options, when to use each, and how to build the UI that works for all of them.
Prerequisites
- FlutterFlow project with Firestore data to search
- Basic understanding of Firestore queries and FlutterFlow ListView
- For Algolia: Algolia account (free tier available) and Cloud Functions enabled
Step-by-step guide
Add a Search TextField with Debounce
Add a Search TextField with Debounce
All three search approaches start the same way: a TextField that triggers a search on change. In FlutterFlow, add a TextField widget to the top of your search page. Set its On Text Changed action to update a Page State variable called searchQuery. To debounce (avoid firing a Firestore query or API call on every single keystroke), add a Custom Action called debounceSearch that uses a Timer to wait 300 milliseconds after the last keystroke before updating an activeSearchQuery Page State variable. Your ListView binds to activeSearchQuery rather than the raw TextField value. This prevents expensive queries while the user is still typing.
1import 'dart:async';23Timer? _debounceTimer;45void debounceSearch(6 String query,7 Future<void> Function(String) onSearch, {8 Duration delay = const Duration(milliseconds: 300),9}) {10 _debounceTimer?.cancel();11 _debounceTimer = Timer(delay, () => onSearch(query));12}Expected result: Typing in the search field shows results after a brief pause rather than flickering on every keystroke.
Option A — Client-Side Filter for Small Lists
Option A — Client-Side Filter for Small Lists
For collections under 500 items, fetch everything into a local list and filter client-side. In FlutterFlow, set up a Firestore query that fetches all documents (with no filter) into a Page State variable as a List type on page load. In your ListView's Filter section, add a Filter condition that checks if the relevant text field contains the searchQuery Page State variable (case-insensitive). FlutterFlow's built-in ListView filtering handles this without any custom code. This approach has zero latency after the initial load, works offline with Firestore persistence, and requires no backend configuration.
Expected result: Typing 'blue' in the search field instantly narrows a 200-item product list to only products with 'blue' in their name or description.
Option B — Firestore Prefix Search for Medium Collections
Option B — Firestore Prefix Search for Medium Collections
Firestore does not have native full-text search, but it supports range queries that enable prefix search. Store a searchable_name field on each document containing the lowercase version of the searchable text. To search for a prefix, query where searchable_name >= searchQuery.toLowerCase() AND searchable_name < searchQuery.toLowerCase() + '\uf8ff'. The \uf8ff character is the highest Unicode code point, so this range matches any string starting with the search query. Create a Firestore index on searchable_name for this query to work efficiently. This approach works for collections up to 5,000-10,000 documents before query times become noticeable.
1import 'package:cloud_firestore/cloud_firestore.dart';23Future<List<Map<String, dynamic>>> prefixSearch(4 String collectionName,5 String searchableField,6 String query, {7 int limit = 20,8}) async {9 if (query.trim().isEmpty) return [];1011 final lowerQuery = query.trim().toLowerCase();12 // Unicode end-of-range marker for prefix search13 final endQuery = lowerQuery + '\uf8ff';1415 final snapshot = await FirebaseFirestore.instance16 .collection(collectionName)17 .where(searchableField, isGreaterThanOrEqualTo: lowerQuery)18 .where(searchableField, isLessThanOrEqualTo: endQuery)19 .limit(limit)20 .get();2122 return snapshot.docs.map((d) => {'id': d.id, ...d.data()}).toList();23}Expected result: Typing 'app' finds all products starting with 'app' (apple, application, appetizer) within 200-400ms from a 5,000-item Firestore collection.
Option C — Algolia for Full-Text Search on Large Collections
Option C — Algolia for Full-Text Search on Large Collections
For collections over 5,000 items or when you need mid-word search (finding 'apple' when searching 'ppl'), use Algolia. Create an Algolia account and get your Application ID, Search-Only API Key, and Admin API Key. Use the Firebase Extension 'Search with Algolia' to automatically sync Firestore writes to your Algolia index. In FlutterFlow, create an API call in the API manager pointing to https://[APP_ID]-dsn.algolia.net/1/indexes/[INDEX_NAME]/query. Pass X-Algolia-Application-Id and X-Algolia-API-Key headers with your public search key. Wire the search TextField to trigger this API call and bind the hits array from the response to your ListView.
1// FlutterFlow API Manager configuration for Algolia search2// Method: POST3// URL: https://YOUR_APP_ID-dsn.algolia.net/1/indexes/YOUR_INDEX_NAME/query4// Headers:5// X-Algolia-Application-Id: YOUR_APP_ID6// X-Algolia-API-Key: YOUR_SEARCH_ONLY_API_KEY (public, safe in client)7// Body (JSON):8{9 "query": "[searchQuery variable]",10 "hitsPerPage": 20,11 "attributesToRetrieve": ["objectID", "name", "description", "price", "image_url"],12 "typoTolerance": true,13 "ignorePlurals": true14}Expected result: Searching 'appls' finds 'apples' and 'apple juice' correctly via typo tolerance. Search results return within 50-100ms for collections of any size.
Display Search Results in a Responsive ListView
Display Search Results in a Responsive ListView
Whether using client-side filtering, Firestore prefix search, or Algolia, wire your search results to a ListView. Add three states to your search results area: an empty query state ('Start typing to search'), a loading state (CircularProgressIndicator visible during the Firestore/API call), and the results list (or an empty results message if the query returns nothing). In FlutterFlow, use a conditional visibility stack: show the empty-state widget when searchQuery is empty, show the loading widget when isSearching Page State is true, and show the ListView when results are available. Add a result count Text widget above the list ('23 results for apple').
Expected result: The search UI transitions smoothly between the three states. Empty searches show a helpful prompt, active searches show a spinner, and results show with a count.
Complete working example
1// FlutterFlow Custom Actions for all three search approaches2import 'package:cloud_firestore/cloud_firestore.dart';3import 'dart:async';45// ─── Debounce Helper ─────────────────────────────────────────────────────────67Timer? _searchDebounce;89void debounceSearch(String query, Function(String) callback) {10 _searchDebounce?.cancel();11 _searchDebounce = Timer(const Duration(milliseconds: 300), () => callback(query));12}1314// ─── Client-Side Filter (for use with a pre-loaded list) ─────────────────────1516List<Map<String, dynamic>> filterList(17 List<Map<String, dynamic>> items,18 String query,19 List<String> searchFields,20) {21 if (query.trim().isEmpty) return items;22 final lower = query.toLowerCase();23 return items.where((item) {24 return searchFields.any((field) {25 final value = item[field]?.toString().toLowerCase() ?? '';26 return value.contains(lower);27 });28 }).toList();29}3031// ─── Firestore Prefix Search ─────────────────────────────────────────────────3233Future<List<Map<String, dynamic>>> prefixSearch(34 String collection,35 String field,36 String query, [37 int limit = 20,38]) async {39 if (query.trim().isEmpty) return [];40 final q = query.trim().toLowerCase();41 final snap = await FirebaseFirestore.instance42 .collection(collection)43 .where(field, isGreaterThanOrEqualTo: q)44 .where(field, isLessThanOrEqualTo: '$q\uf8ff')45 .limit(limit)46 .get();47 return snap.docs.map((d) => {'id': d.id, ...d.data()}).toList();48}4950// ─── Multi-Field Prefix Search ────────────────────────────────────────────────51// Searches across multiple indexed fields and deduplicates results5253Future<List<Map<String, dynamic>>> multiFieldPrefixSearch(54 String collection,55 List<String> fields,56 String query, [57 int limit = 20,58]) async {59 if (query.trim().isEmpty) return [];60 final q = query.trim().toLowerCase();6162 final futures = fields.map((field) =>63 FirebaseFirestore.instance64 .collection(collection)65 .where(field, isGreaterThanOrEqualTo: q)66 .where(field, isLessThanOrEqualTo: '$q\uf8ff')67 .limit(limit)68 .get(),69 );7071 final snapshots = await Future.wait(futures);72 final seen = <String>{};73 final results = <Map<String, dynamic>>[];7475 for (final snap in snapshots) {76 for (final doc in snap.docs) {77 if (seen.add(doc.id)) {78 results.add({'id': doc.id, ...doc.data()});79 }80 }81 }8283 return results;84}Common mistakes when adding a Search Feature to Your FlutterFlow App
Why it's a problem: Using Firestore where with isEqualTo for search — only matches exact full values
How to avoid: Use the range query prefix search pattern (>= query, <= query + \uf8ff) on a lowercase field for Firestore-native search, or integrate Algolia for full-text capabilities including mid-word matching and typo tolerance.
Why it's a problem: Fetching all documents for client-side search on large collections
How to avoid: Client-side search is only appropriate for collections under 500 documents. For larger datasets, use Firestore prefix search or Algolia.
Why it's a problem: Not debouncing the search TextField, triggering a query on every keystroke
How to avoid: Add 300ms debounce to the On Text Changed action so queries only fire after the user pauses typing.
Best practices
- Always store a lowercase copy of searchable text fields (e.g., name_lower) for case-insensitive Firestore prefix search.
- Add a minimum query length of 2-3 characters before triggering any search to prevent overly broad result sets.
- Show a 'No results for X. Try a different search.' message when the query returns empty — never show a blank ListView.
- Add a Clear button (X icon) inside the search TextField that clears the query and returns to the default list view.
- For Algolia, use the Search-Only API Key in the client and the Admin Key only in Cloud Functions — never swap these.
- Index the search field in Firestore Console when using prefix search — uninexed range queries will fail or perform a full collection scan.
- Consider adding recent searches stored in local storage or App State so users can repeat common queries without retyping.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am building search functionality in a FlutterFlow app with Firebase. Explain the three search approaches: client-side list filtering, Firestore range query prefix search, and Algolia full-text search. For each approach, show when to use it (by data size), how to implement it in Dart for a Custom Action, how to add debounce to the search TextField, and how to display loading, empty, and results states in the ListView.
In my FlutterFlow app, add a search TextField to my ProductsPage that debounces input to 300ms before updating a Page State variable activeSearchQuery. Create a Custom Action called prefixSearchProducts(query) that searches the products Firestore collection using a range query on the name_lower field and returns the matching documents. Show a loading spinner while the query is running and a 'No results' message when the list is empty.
Frequently asked questions
How do I search across multiple fields at once, like name AND description?
For client-side filtering, iterate over multiple fields in your filter function. For Firestore prefix search, you need a separate index field that concatenates all searchable text (e.g., search_blob = name + ' ' + description + ' ' + tags.join(' '), lowercased). Firestore cannot query across multiple fields with range operators simultaneously without this denormalized field. Algolia handles multi-field search natively in its index configuration.
Does Algolia have a free tier?
Yes. Algolia's free tier includes 10,000 search requests per month and 10,000 records per index, which is sufficient for small apps. Beyond that, plans start at $0.50 per 1,000 searches. For most FlutterFlow apps, the free tier is adequate during development and early production.
Can I search Firestore for numbers and dates, not just text?
Yes. Firestore supports range queries on numeric and timestamp fields natively. For price range search, use .where('price', isGreaterThanOrEqualTo: minPrice).where('price', isLessThanOrEqualTo: maxPrice). For date range, the same pattern works with Timestamp values. These numeric queries do not require the special lowercase field trick needed for text prefix search.
What is Typesense and how does it compare to Algolia?
Typesense is an open-source alternative to Algolia. It is self-hosted (runs on your own server or Cloud Run) which makes it free at the cost of infrastructure management, or available as Typesense Cloud for $15/month and up. It has very similar full-text search capabilities to Algolia. For FlutterFlow apps hosted on Firebase, Algolia's Firebase Extension makes setup much easier — Typesense requires more manual integration work.
How do I highlight the matching search term in the results?
For client-side filtering, split the result text on the search query and wrap the matching portion in a Text widget with bold or colored styling using RichText with TextSpan children. Algolia returns highlight data in its response (hits[].X._highlightResult) with matched portions pre-marked — parse this in a Custom Widget to render the highlighted result. FlutterFlow's standard Text widget cannot do inline highlighting without a Custom Widget.
Why are my Firestore prefix search results not case-insensitive?
Firestore string comparisons are case-sensitive. Searching for 'Apple' will not find a document with name 'apple'. The fix is always: store a lowercase copy of the field (e.g., name_lower = name.toLowerCase()) when creating documents, search using the lowercase query on the lowercase field, and display the original mixed-case field in your results.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation