Skip to content

Firestore Queries Deep Dive: Filters, Sorting, Pagination & Indexes

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Firestore Queries Deep Dive: Filters, Sorting, Pagination & Indexes. We cover key concepts, practical examples, and best practices to help you master this topic.

Firestore queries use indexes to filter, sort, and paginate documents with compound where clauses, orderBy, limit, cursors, and collection group queries for efficient data retrieval.

What You'll Learn

How to build efficient Firestore queries with multiple filters, sort results, paginate with cursors, query across subcollections, and create Composite indexes for query performance.

Why It Matters

Poor queries cost money and slow down your app. Each document read is billed, and unindexed queries fail at scale. DodaTech's threat dashboard queries 50K+ scan records daily with sub-second response times using well-designed indexes.

Real-World Use

A security operations dashboard: filter scans by date range and severity, sort by threat count, paginate results 25 at a time, and query across all devices for enterprise-wide reports.

flowchart LR
    A["Query Request\nFilter + Sort"] --> B{"Has Index?"}
    B -->|Yes| C["Return Results\nSub-second"]
    B -->|No| D["Error: needs\ncomposite index"]
    D --> E["Create Index\nConsole / CLI"]
    E --> C
    style C fill:#bbf7d0,stroke:#16a34a
    style D fill:#fecaca,stroke:#dc2626

Basic Filtering

import { collection, query, where, getDocs } from "firebase/firestore";

// Single condition filter
async function getHighThreatScans() {
  const q = query(
    collection(db, "scans"),
    where("severity", "==", "high")
  );
  const snapshot = await getDocs(q);
  snapshot.forEach(doc => console.log(doc.id, doc.data()));
}
// Expected output: scan_001 { severity: "high", deviceId: "dev_abc", ... }
//                  scan_002 { severity: "high", deviceId: "dev_def", ... }

Compound Queries

import { query, where, orderBy, limit } from "firebase/firestore";

// Multiple conditions with ordering
async function getRecentCriticalScans() {
  const q = query(
    collection(db, "scans"),
    where("severity", "==", "critical"),
    orderBy("timestamp", "desc"),
    limit(10)
  );
  const snapshot = await getDocs(q);
  console.log("Recent critical scans:", snapshot.size);
}
// Expected output: Recent critical scans: 10

Pagination with Cursors

import { query, orderBy, limit, startAfter, getDocs } from "firebase/firestore";

async function paginateScans(lastDoc) {
  const q = query(
    collection(db, "scans"),
    orderBy("timestamp", "desc"),
    startAfter(lastDoc),
    limit(25)
  );
  const snapshot = await getDocs(q);
  const lastVisible = snapshot.docs[snapshot.docs.length - 1];
  console.log("Page size:", snapshot.size, "Next cursor:", lastVisible?.id);
}
// Expected output: Page size: 25 Next cursor: scan_026

Collection Group Queries

import { collectionGroup, query, where } from "firebase/firestore";

// Query across all subcollections named "scans"
async function getAllScansForUser(userId) {
  const q = query(
    collectionGroup(db, "scans"),
    where("userId", "==", userId)
  );
  const snapshot = await getDocs(q);
  console.log("Total scans across all devices:", snapshot.size);
}
// Expected output: Total scans across all devices: 47

Creating Composite Indexes

// When you run a compound query without an index, Firestore throws an error
// with a direct link to create the required index.

async function queryNeedingIndex() {
  const q = query(
    collection(db, "scans"),
    where("severity", "==", "high"),
    where("deviceOs", "==", "Windows"),
    orderBy("timestamp", "desc")
  );
  try {
    await getDocs(q);
  } catch (err) {
    console.log("Index needed:", err.message);
    // Error includes link to Firebase Console to create index
  }
}
// Expected output: Index needed: The query requires an index. You can create it here: https://console.firebase.google.com/...

Common Mistakes

1. Querying Without an Index

Firestore requires indexes for compound queries. The error message includes a direct link to create the needed index — use it immediately.

2. Using Range Filters on Multiple Fields

Firestore only supports range conditions (<, <=, >, >=, !=) on one field per query. Combine range with equality on other fields using composite indexes.

3. Forgetting orderBy Direction in Index

Composite indexes must include the sort direction (asc/desc) matching your orderBy. Mismatched directions cause query failures.

4. Pagination Without Cursors

Using offset instead of cursors is expensive — Firestore reads every skipped document. Use startAfter or startAt with a document snapshot.

5. Not Using select() to Reduce Read Costs

// Fetch only specific fields to reduce read costs
import { select } from "firebase/firestore";
const q = query(
  collection(db, "scans"),
  select("severity", "timestamp", "deviceId")
);

Practice Questions

  1. What is a composite index and when do you need one?
  2. How does cursor-based pagination differ from offset pagination?
  3. What is a collection group query and when would you use it?
  4. Why can Firestore only support range filters on one field?

Answers:

  1. A composite index indexes multiple fields together, required for compound queries with equality + range or multiple equality filters.
  2. Cursor pagination (startAfter, startAt) reads only the requested page. Offset reads all skipped documents, costing more reads.
  3. Collection group queries search across all subcollections with the same name, useful for querying data across a hierarchy.
  4. Firestore's index structure stores data ordered by the first indexed field, then the second. Multiple range fields would require complex multi-dimensional indexing.

Challenge: Build a paginated threat-feed endpoint that filters by severity (equality), sorts by timestamp (descending, limit 20), uses cursor pagination, and creates the composite index through the Firebase Console.

FAQ

Why does my compound query fail with an index error?

Firestore requires composite indexes for queries with multiple where clauses or combined where + orderBy. The error message provides a direct Console link to create it.

How many composite indexes can I create?

Firestore supports up to 200 composite indexes per database. You can request increases through support.

What is the difference between `startAt` and `startAfter`?

startAt includes the specified document in results; startAfter excludes it. Use startAfter for pages after the first.

Do collection group queries require special indexes?

Yes, you need to create an index with the Collection Group scope option in the Firebase Console for field combinations used in collection group queries.

How does Firestore handle query performance at scale?

Firestore uses the result set size, not the collection size, to determine performance. A query returning 10 results from 10M documents is as fast as from 100 documents.

Mini Project

Create an advanced query layer for Durga Antivirus Pro: filter scans by severity + date range, sort by threat count descending, paginate with cursors (25 per page), query across all devices with collection group, and create the required composite indexes.

What's Next

Firestore Security Rules — secure your Firestore data with access control rules.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro