Firestore Queries Guide: Filter, Sort, Index & Paginate Data
Firestore queries filter, sort, and paginate data using the document/collection model with where clauses, range filters, and composite indexes.
What You’ll Learn
- Basic queries with
where()filters and comparison operators - Sorting with
orderBy()and limiting results - Compound queries and composite indexes
- Pagination with cursors and
limit() - Collection group queries for cross-collection search
- Real-time query listeners
Why Queries Matter
Storing data is useless if you can’t retrieve it efficiently. Firestore’s query engine lets you filter millions of documents by field values, sort by timestamps, and paginate results — all with millisecond latency. DodaTech’s Durga Antivirus Pro queries Firestore to find all devices with critical threats, sort by severity, and paginate results for the dashboard — returning only the most urgent threats without scanning every document.
flowchart LR
A["Firestore\nCollection"] -->|"where('severity','==','critical')"| B["Filtered Results"]
A -->|"orderBy('detectedAt','desc')"| C["Sorted Results"]
A -->|"limit(20) + startAfter(cursor)"| D["Paginated Results"]
B --> E["Queried Results"]
C --> E
D --> E
style A fill:#dbeafe,stroke:#2563eb
style E fill:#fef3c7,stroke:#d97706
Basic Queries
Simple Where Filter
import { collection, query, where, getDocs } from 'firebase/firestore';
// Find all critical threats
const q = query(collection(db, "threats"),
where("severity", "==", "critical")
);
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.id, "=>", doc.data().name);
});Comparison Operators
Firestore supports <, <=, ==, >=, >, !=, array-contains, in, and not-in:
// Threats detected in the last 24 hours
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
const q = query(collection(db, "threats"),
where("detectedAt", ">=", yesterday)
);
// Threats with specific names
const q = query(collection(db, "threats"),
where("name", "in", ["Emotet", "LockBit", "BlackCat"])
);Sorting with orderBy
import { query, orderBy, limit } from 'firebase/firestore';
// Most recent threats first, limit to 10
const q = query(collection(db, "threats"),
orderBy("detectedAt", "desc"),
limit(10)
);Important rule: If you use a where() filter with a range comparison (<, <=, >, >=, !=), the first orderBy() must be on the same field as the range filter.
// ✅ Valid: orderBy matches the range field
const q = query(collection(db, "threats"),
where("detectedAt", ">=", yesterday),
orderBy("detectedAt", "desc")
);
// ❌ Invalid: range on 'detectedAt' but first orderBy is 'name'
const q = query(collection(db, "threats"),
where("detectedAt", ">=", yesterday),
orderBy("name", "asc")
);Compound Queries and Composite Indexes
When you filter and sort on different fields, Firestore needs a composite index:
// This query needs a composite index on [severity, detectedAt]
const q = query(collection(db, "threats"),
where("severity", "==", "critical"),
orderBy("detectedAt", "desc"),
limit(20)
);What happens without the index? Firestore returns an error with a direct link to create the index in the console. Click the link, confirm, and the index is ready in minutes.
// Creating the index via Firebase CLI
// firebase firestore:indexes
// Output in firestore.indexes.json:
{
"indexes": [
{
"collectionGroup": "threats",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "severity", "order": "ASCENDING" },
{ "fieldPath": "detectedAt", "order": "DESCENDING" }
]
}
]
}Pagination
Offset Pagination (Simple)
const q = query(collection(db, "threats"),
orderBy("detectedAt", "desc"),
limit(20)
);
const snapshot = await getDocs(q);
// Page 2 — get next 20
const nextQ = query(collection(db, "threats"),
orderBy("detectedAt", "desc"),
limit(20),
startAfter(snapshot.docs[snapshot.docs.length - 1])
);Cursor Pagination
// Start from a specific document
const q = query(collection(db, "threats"),
orderBy("detectedAt", "desc"),
limit(20),
startAfter(doc(db, "threats", "threat-050"))
);
// Previous page
const prevQ = query(collection(db, "threats"),
orderBy("detectedAt", "desc"),
limit(20),
endBefore(doc(db, "threats", "threat-050"))
);Why not use offset? Firestore doesn’t support offset() on large datasets — it would bill for reads of skipped documents. Cursor-based pagination using startAfter and endBefore is efficient and recommended.
Collection Group Queries
Query across all subcollections with the same name, not just under one parent:
import { collectionGroup, query, where } from 'firebase/firestore';
// Find all threats across all devices (not just one device's subcollection)
const q = query(collectionGroup(db, "threats"),
where("severity", "==", "critical")
);
const snapshot = await getDocs(q);
snapshot.forEach((doc) => {
console.log(`Threat ${doc.data().name} found in ${doc.ref.path}`);
});
// Output: Threat Emotet found in devices/device-abc/threats/threat-001
// Threat LockBit found in devices/device-def/threats/threat-002
Common Mistakes
1. Querying Without Filters
Reading an entire collection (getDocs(collection(db, "devices"))) on a production app reads every document — expensive and slow. Always use where() and limit().
2. Missing Composite Index Errors
Adding orderBy to a filtered query often requires a composite index. The error message includes a clickable link to create it — don’t ignore it.
3. Using != on Large Collections
The != (not-equal) query requires a composite index and can be slow on large collections. If possible, rephrase as a positive match (e.g., in with specific values instead of !=).
4. Forgetting Query Speed Is Tied to Indexes
Every query in Firestore uses an index. If your query pattern isn’t covered by an existing index, create one. Well-indexed queries return in milliseconds; unindexed queries fail.
5. Not Using limit() in List Views
Without limit(), queries may return thousands of documents, causing slow renders and high Firestore bills. Always paginate with limit().
Practice Questions
- What is a composite index and when do you need one?
- Why doesn’t Firestore support
OFFSETfor pagination? - What is a collection group query and when would you use it?
- What happens if you use
where()with a range filter but orderBy on a different field? - How do you paginate forward using cursor-based pagination?
Answers:
- A composite index indexes multiple fields together. You need one when you filter on one field and sort on a different field.
- Offset would require reading and billing for all skipped documents. Cursor-based pagination is more efficient because it uses the index directly.
- A collection group query searches across all subcollections with the same name, regardless of parent document. Useful for finding all threats across all devices.
- The query fails with an error requiring a composite index that includes both fields.
- Use
startAfter(lastDoc)wherelastDocis the last document from the previous page, combined withorderByandlimit.
Challenge: Write a Firestore query for Durga Antivirus Pro that finds the 10 most recent high-severity threats across all devices, sorted by detection time descending. Then write the paginated version for page 2. Include the composite index configuration.
FAQ
Try It Yourself
// Query all critical threats, sorted by detection time
import { collection, query, where, orderBy, limit, getDocs } from 'firebase/firestore';
async function getCriticalThreats() {
const q = query(
collection(db, "threats"),
where("severity", "==", "critical"),
orderBy("detectedAt", "desc"),
limit(10)
);
const snapshot = await getDocs(q);
snapshot.forEach(doc => console.log(doc.data().name, doc.data().detectedAt));
}What’s Next
| Topic | Description |
|---|---|
| Security Rules & Hosting | Protect your data and deploy app |
| Firestore & Realtime DB | Review data modeling fundamentals |
| Firebase Auth Guide | Authenticate users for query access |
| MongoDB Queries | Compare NoSQL query patterns across databases |
What’s Next
Congratulations on completing this Firebase Queries tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro