Firestore queries are case-sensitive by default. To make them case-insensitive, store a lowercase copy of each searchable field (for example, name_lowercase) and query against that normalized field. Use a Cloud Function or client-side logic to auto-generate the lowercase field on every write, ensuring consistent search results regardless of how users type their queries.
Making Firestore Queries Case-Insensitive
Firestore does not support case-insensitive queries natively. A query for 'John' will not match 'john' or 'JOHN'. The standard solution is to store a lowercase copy of each searchable text field and query against that normalized field. This tutorial shows you how to create the lowercase field, automate it with a Cloud Function trigger, batch-update existing documents, and query the normalized field for consistent case-insensitive results.
Prerequisites
- A Firebase project with Firestore enabled
- Firebase JS SDK v9+ installed in your project
- Node.js 18+ for Cloud Functions (optional but recommended)
- Basic understanding of Firestore document structure
Step-by-step guide
Store a lowercase copy of the searchable field
Store a lowercase copy of the searchable field
When writing a document to Firestore, add an extra field that contains the lowercase version of your searchable text. For example, if you have a name field, also store name_lowercase. This keeps the original casing for display while providing a normalized version for queries. Apply this pattern to every field you want to search case-insensitively.
1import { getFirestore, collection, addDoc, serverTimestamp } from 'firebase/firestore';23const db = getFirestore();45async function addUser(name: string, email: string) {6 const docRef = await addDoc(collection(db, 'users'), {7 name: name,8 name_lowercase: name.toLowerCase(),9 email: email,10 email_lowercase: email.toLowerCase(),11 createdAt: serverTimestamp()12 });13 console.log('User added with ID:', docRef.id);14 return docRef;15}Expected result: Each new document has both the original field and a lowercase copy stored alongside it.
Query using the lowercase field
Query using the lowercase field
When searching, convert the user's input to lowercase and query the normalized field instead of the original. This ensures that searching for 'john', 'John', or 'JOHN' all return the same results. For prefix searches (starts with), use the >= and < operators with the lowercase input.
1import { getFirestore, collection, query, where, getDocs } from 'firebase/firestore';23const db = getFirestore();45async function searchUsers(searchTerm: string) {6 const normalizedTerm = searchTerm.toLowerCase();78 // Exact match9 const exactQuery = query(10 collection(db, 'users'),11 where('name_lowercase', '==', normalizedTerm)12 );1314 // Prefix search (starts with)15 const prefixQuery = query(16 collection(db, 'users'),17 where('name_lowercase', '>=', normalizedTerm),18 where('name_lowercase', '<=', normalizedTerm + '\uf8ff')19 );2021 const snapshot = await getDocs(prefixQuery);22 return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));23}Expected result: Queries return matching documents regardless of the original casing of the stored data.
Automate lowercase field generation with a Cloud Function
Automate lowercase field generation with a Cloud Function
To avoid manually adding lowercase fields in every write operation, create a Cloud Function that triggers on document creation and update. The function reads the original fields, generates lowercase versions, and writes them back to the document. This ensures every document has normalized fields even if written from different clients or the Firebase Console.
1import { onDocumentWritten } from 'firebase-functions/v2/firestore';2import { getFirestore } from 'firebase-admin/firestore';3import { initializeApp } from 'firebase-admin/app';45initializeApp();6const db = getFirestore();78export const normalizeUserFields = onDocumentWritten(9 'users/{userId}',10 async (event) => {11 const after = event.data?.after?.data();12 if (!after) return; // Document was deleted1314 const updates: Record<string, string> = {};1516 if (after.name && after.name_lowercase !== after.name.toLowerCase()) {17 updates.name_lowercase = after.name.toLowerCase();18 }19 if (after.email && after.email_lowercase !== after.email.toLowerCase()) {20 updates.email_lowercase = after.email.toLowerCase();21 }2223 if (Object.keys(updates).length > 0) {24 await event.data?.after?.ref.update(updates);25 }26 }27);Expected result: Every time a user document is created or updated, the Cloud Function automatically generates or updates the lowercase fields.
Batch-update existing documents with lowercase fields
Batch-update existing documents with lowercase fields
If you already have documents without lowercase fields, write a one-time migration script using the Firebase Admin SDK. This script reads all documents in a collection, generates the lowercase values, and writes them back using batch writes for efficiency. The 500-operation limit per batch means you need to commit and create new batches for large collections.
1import { initializeApp, cert } from 'firebase-admin/app';2import { getFirestore } from 'firebase-admin/firestore';34initializeApp();5const db = getFirestore();67async function migrateToLowercase() {8 const usersRef = db.collection('users');9 const snapshot = await usersRef.get();10 let batch = db.batch();11 let count = 0;1213 for (const doc of snapshot.docs) {14 const data = doc.data();15 const updates: Record<string, string> = {};1617 if (data.name) updates.name_lowercase = data.name.toLowerCase();18 if (data.email) updates.email_lowercase = data.email.toLowerCase();1920 if (Object.keys(updates).length > 0) {21 batch.update(doc.ref, updates);22 count++;23 }2425 if (count >= 499) {26 await batch.commit();27 batch = db.batch();28 count = 0;29 }30 }3132 if (count > 0) await batch.commit();33 console.log('Migration complete');34}3536migrateToLowercase();Expected result: All existing documents now have lowercase fields and are searchable case-insensitively.
Update Firestore security rules for the new fields
Update Firestore security rules for the new fields
Add security rules that protect the lowercase fields from being set to incorrect values by clients. The rules should validate that the lowercase field matches the lowercase version of the original field. This prevents clients from setting arbitrary values in the normalized fields, which would break search consistency.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {4 match /users/{userId} {5 allow read: if request.auth != null;6 allow create, update: if request.auth != null7 && request.resource.data.name is string8 && request.resource.data.name_lowercase == request.resource.data.name.lower();9 }10 }11}Expected result: Security rules reject writes where the lowercase field does not match the lowercased original field.
Complete working example
1import {2 getFirestore,3 collection,4 addDoc,5 query,6 where,7 getDocs,8 serverTimestamp9} from 'firebase/firestore';10import { initializeApp } from 'firebase/app';1112const firebaseConfig = {13 apiKey: 'YOUR_API_KEY',14 authDomain: 'YOUR_PROJECT.firebaseapp.com',15 projectId: 'YOUR_PROJECT_ID',16 storageBucket: 'YOUR_PROJECT.appspot.com',17 messagingSenderId: 'YOUR_SENDER_ID',18 appId: 'YOUR_APP_ID'19};2021const app = initializeApp(firebaseConfig);22const db = getFirestore(app);2324// Add a document with normalized lowercase fields25export async function addUser(name: string, email: string) {26 return addDoc(collection(db, 'users'), {27 name,28 name_lowercase: name.toLowerCase(),29 email,30 email_lowercase: email.toLowerCase(),31 createdAt: serverTimestamp()32 });33}3435// Case-insensitive exact search36export async function findUserByName(searchName: string) {37 const q = query(38 collection(db, 'users'),39 where('name_lowercase', '==', searchName.toLowerCase())40 );41 const snapshot = await getDocs(q);42 return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));43}4445// Case-insensitive prefix search46export async function searchUsersByPrefix(prefix: string) {47 const normalizedPrefix = prefix.toLowerCase();48 const q = query(49 collection(db, 'users'),50 where('name_lowercase', '>=', normalizedPrefix),51 where('name_lowercase', '<=', normalizedPrefix + '\uf8ff')52 );53 const snapshot = await getDocs(q);54 return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));55}Common mistakes when making Firestore Queries Case-Insensitive
Why it's a problem: Querying the original field instead of the lowercase field
How to avoid: Always query the _lowercase version of the field. Update your search functions to use where('name_lowercase', '==', input.toLowerCase()) instead of where('name', '==', input).
Why it's a problem: Forgetting to update the lowercase field when the original field changes
How to avoid: Use a Cloud Function trigger on onDocumentWritten to automatically regenerate lowercase fields on every write, or ensure all client-side update code includes the lowercase field.
Why it's a problem: Creating an infinite loop in the Cloud Function that normalizes fields
How to avoid: Always check if the lowercase field already matches before writing. Compare after.name_lowercase !== after.name.toLowerCase() to avoid triggering the function again on its own update.
Best practices
- Use a consistent naming convention like fieldName_lowercase for all normalized fields
- Automate lowercase field generation with Cloud Functions instead of relying on client-side code
- Validate lowercase fields in Firestore security rules using the .lower() string method
- Run a one-time batch migration to add lowercase fields to existing documents
- Index the lowercase fields for query performance, especially for prefix searches
- Consider storing additional normalized versions (trimmed, accent-stripped) for international text
- Document the normalization pattern in your project so all developers follow the same approach
- For full-text search beyond prefix matching, consider integrating Algolia or Typesense
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Firestore collection with a name field and I need case-insensitive search. Show me how to store a lowercase copy of the field, query it, and write a Cloud Function that automatically generates the lowercase field on every write.
Write a Firebase Cloud Function v2 that triggers on document writes in a users collection and automatically generates lowercase versions of the name and email fields. Include the security rules that validate the lowercase fields match.
Frequently asked questions
Does Firestore support case-insensitive queries natively?
No. Firestore queries are strictly case-sensitive. There is no built-in operator or query modifier for case-insensitive matching. The standard workaround is storing a lowercase copy of each searchable field and querying that instead.
Will storing lowercase fields increase my Firestore costs?
Marginally. Each additional field adds a small amount of storage and an automatic single-field index. For most applications, this overhead is negligible compared to the benefit of reliable search.
Can I use Firestore security rules to enforce lowercase field consistency?
Yes. Firestore rules support the .lower() method on strings. You can add a rule like request.resource.data.name_lowercase == request.resource.data.name.lower() to ensure clients always write correct lowercase values.
What about case-insensitive search for non-Latin characters?
The toLowerCase() JavaScript method handles most Unicode characters correctly, including accented Latin characters and Cyrillic. For languages with complex case rules (like Turkish dotted/dotless i), use toLocaleLowerCase('tr') with the appropriate locale.
Should I use a Cloud Function or handle lowercase on the client?
A Cloud Function is more reliable because it catches writes from all sources including the Firebase Console, Admin SDK, and different client apps. Client-side normalization works for simple setups but risks inconsistency if any write path skips the normalization.
How do I handle case-insensitive search across multiple fields?
Create a lowercase copy for each searchable field. For combined search, consider a search_keywords array containing lowercase tokens from all searchable fields, then use array-contains queries against it.
Can RapidDev help implement case-insensitive search in my Firebase app?
Yes. RapidDev can set up the full case-insensitive search pattern for your Firebase project, including Cloud Functions for automated normalization, batch migration of existing data, and integration with external search services like Algolia for more advanced search needs.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation