Skip to main content
RapidDev - Software Development Agency
firebase-tutorial

How to Use Where Clause in Firestore

Firestore's where clause filters documents using operators like ==, !=, <, >, in, not-in, array-contains, and array-contains-any. Chain multiple where calls for compound queries, but follow Firestore's rules: range filters on different fields require a composite index, and you can only use one array-contains or one in operator per query. Use the modular v9+ query and where imports for tree-shaking.

What you'll learn

  • How to use every where operator including ==, !=, <, >, in, not-in, array-contains, and array-contains-any
  • How to build compound queries with multiple where clauses
  • How to understand and create composite indexes for complex queries
  • How to combine where with orderBy and limit for efficient pagination
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate8 min read12-18 minFirebase JS SDK v9+, Firestore (all plans)March 2026RapidDev Engineering Team
TL;DR

Firestore's where clause filters documents using operators like ==, !=, <, >, in, not-in, array-contains, and array-contains-any. Chain multiple where calls for compound queries, but follow Firestore's rules: range filters on different fields require a composite index, and you can only use one array-contains or one in operator per query. Use the modular v9+ query and where imports for tree-shaking.

Filtering Firestore Documents with the Where Clause

The where clause is the primary way to filter documents in Firestore queries. This tutorial covers every operator, shows how to chain multiple conditions for compound queries, explains when composite indexes are needed, and demonstrates common patterns like range queries, membership checks, and array filtering. All examples use the modular v9+ SDK syntax.

Prerequisites

  • A Firebase project with Firestore database created
  • Firebase JS SDK v9+ installed
  • At least one Firestore collection with documents to query
  • Basic understanding of Firestore documents and collections

Step-by-step guide

1

Use basic equality and comparison operators

The most common operators are == for exact match and <, >, <=, >= for range comparisons. Import query, where, collection, and getDocs from firebase/firestore. Build the query by chaining where clauses inside the query function.

typescript
1import { getFirestore, collection, query, where, getDocs } from 'firebase/firestore'
2
3const db = getFirestore()
4
5// Exact match
6const activeUsersQuery = query(
7 collection(db, 'users'),
8 where('status', '==', 'active')
9)
10
11// Range comparison
12const expensiveProductsQuery = query(
13 collection(db, 'products'),
14 where('price', '>', 100)
15)
16
17const snapshot = await getDocs(activeUsersQuery)
18snapshot.forEach((doc) => {
19 console.log(doc.id, doc.data())
20})

Expected result: Only documents matching the where condition are returned.

2

Filter with in and not-in operators

The in operator matches documents where a field equals any value in a provided array (up to 30 values). The not-in operator matches documents where the field does not equal any value in the array (up to 10 values). These are useful for matching multiple possible values without writing separate queries.

typescript
1// Match any of these categories
2const categoriesQuery = query(
3 collection(db, 'products'),
4 where('category', 'in', ['electronics', 'clothing', 'books'])
5)
6
7// Exclude specific statuses
8const nonDraftQuery = query(
9 collection(db, 'posts'),
10 where('status', 'not-in', ['draft', 'archived'])
11)

Expected result: Documents matching any of the specified values are returned for in; documents not matching any value are returned for not-in.

3

Query arrays with array-contains and array-contains-any

Use array-contains to find documents where an array field includes a specific value. Use array-contains-any to match documents where the array contains at least one value from a provided list. You can only use one array-contains or array-contains-any per query.

typescript
1// Find posts tagged with 'firebase'
2const firebasePostsQuery = query(
3 collection(db, 'posts'),
4 where('tags', 'array-contains', 'firebase')
5)
6
7// Find posts tagged with any of these
8const multiTagQuery = query(
9 collection(db, 'posts'),
10 where('tags', 'array-contains-any', ['firebase', 'react', 'typescript'])
11)

Expected result: Documents containing the specified values in their array fields are returned.

4

Build compound queries with multiple where clauses

Chain multiple where calls to apply AND logic. Firestore evaluates all conditions and returns only documents matching every clause. Equality filters on different fields work without a composite index. Range filters on multiple different fields or combining range with inequality on another field require a composite index.

typescript
1// Compound equality — no composite index needed
2const adminActiveQuery = query(
3 collection(db, 'users'),
4 where('role', '==', 'admin'),
5 where('status', '==', 'active')
6)
7
8// Equality + range — requires composite index
9const recentElectronicsQuery = query(
10 collection(db, 'products'),
11 where('category', '==', 'electronics'),
12 where('price', '<', 500),
13 where('price', '>', 50)
14)

Expected result: Documents matching all where conditions are returned. If an index is needed, Firestore provides a creation link in the error message.

5

Combine where with orderBy and limit

Add orderBy to sort filtered results and limit to cap the number of documents returned. When using a range filter (>, <, >=, <=), the first orderBy must be on the same field as the range filter. This is a Firestore requirement for index efficiency.

typescript
1import { query, where, orderBy, limit, collection } from 'firebase/firestore'
2
3const topElectronicsQuery = query(
4 collection(db, 'products'),
5 where('category', '==', 'electronics'),
6 orderBy('price', 'asc'),
7 limit(10)
8)
9
10// Range filter requires orderBy on the same field first
11const recentExpensiveQuery = query(
12 collection(db, 'products'),
13 where('price', '>', 100),
14 orderBy('price', 'desc'),
15 orderBy('createdAt', 'desc'),
16 limit(20)
17)

Expected result: Filtered documents are returned sorted by the specified field, limited to the requested count.

6

Use where with real-time listeners

Queries with where clauses work with onSnapshot for real-time updates. Firestore only sends changes that match your filter, so listeners are efficient even on large collections. The listener fires with the initial matching documents and then with updates as documents enter or leave the filter.

typescript
1import { query, where, collection, onSnapshot } from 'firebase/firestore'
2
3const activeOrdersQuery = query(
4 collection(db, 'orders'),
5 where('status', '==', 'pending')
6)
7
8const unsubscribe = onSnapshot(activeOrdersQuery, (snapshot) => {
9 snapshot.docChanges().forEach((change) => {
10 if (change.type === 'added') {
11 console.log('New pending order:', change.doc.data())
12 }
13 if (change.type === 'removed') {
14 console.log('Order no longer pending:', change.doc.id)
15 }
16 })
17})
18
19// Call unsubscribe() when done to stop listening

Expected result: The listener fires when a document matches or no longer matches the where condition, enabling real-time filtered views.

Complete working example

src/lib/firestore-queries.ts
1// src/lib/firestore-queries.ts
2import {
3 getFirestore,
4 collection,
5 query,
6 where,
7 orderBy,
8 limit,
9 getDocs,
10 onSnapshot,
11 type QueryConstraint
12} from 'firebase/firestore'
13
14const db = getFirestore()
15
16// Reusable query builder
17export async function queryCollection(
18 collectionName: string,
19 constraints: QueryConstraint[]
20) {
21 const q = query(collection(db, collectionName), ...constraints)
22 const snapshot = await getDocs(q)
23 return snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }))
24}
25
26// Example: Get active users with a specific role
27export async function getActiveAdmins() {
28 return queryCollection('users', [
29 where('role', '==', 'admin'),
30 where('status', '==', 'active'),
31 orderBy('createdAt', 'desc'),
32 limit(50)
33 ])
34}
35
36// Example: Get products in a price range
37export async function getProductsByPriceRange(
38 minPrice: number,
39 maxPrice: number,
40 category?: string
41) {
42 const constraints: QueryConstraint[] = [
43 where('price', '>=', minPrice),
44 where('price', '<=', maxPrice),
45 orderBy('price', 'asc'),
46 limit(25)
47 ]
48 if (category) {
49 constraints.unshift(where('category', '==', category))
50 }
51 return queryCollection('products', constraints)
52}
53
54// Example: Real-time listener for filtered data
55export function onPendingOrders(
56 callback: (orders: any[]) => void
57) {
58 const q = query(
59 collection(db, 'orders'),
60 where('status', '==', 'pending'),
61 orderBy('createdAt', 'desc')
62 )
63 return onSnapshot(q, (snapshot) => {
64 const orders = snapshot.docs.map((doc) => ({
65 id: doc.id,
66 ...doc.data()
67 }))
68 callback(orders)
69 })
70}

Common mistakes when using Where Clause in Firestore

Why it's a problem: Combining range filters on different fields without creating a composite index, resulting in the error 'The query requires an index'

How to avoid: Click the link in the error message to create the required composite index in the Firebase Console, or add it to firestore.indexes.json and deploy with firebase deploy --only firestore:indexes.

Why it's a problem: Using both array-contains and in in the same query, which Firestore does not allow

How to avoid: Split the query into two separate queries and merge results client-side. Alternatively, restructure your data model to use maps instead of arrays for multi-value filtering.

Why it's a problem: Adding orderBy on a field different from the range filter field as the first sort, causing a Firestore error

How to avoid: When using a range filter (>, <, >=, <=), the first orderBy must be on the same field. Add additional orderBy clauses after.

Why it's a problem: Expecting where to match documents where the field does not exist — Firestore only returns documents where the filtered field is present

How to avoid: Firestore queries only match documents that contain the field being filtered. If you need to find documents missing a field, query all documents and filter client-side, or set a default value on every document.

Best practices

  • Use equality filters before range filters in your query chain for readability and to match Firestore's index requirements
  • Create composite indexes proactively by defining them in firestore.indexes.json rather than waiting for error messages
  • Use limit() on every query to control costs — without it, a query can return an entire collection worth of reads
  • Prefer array-contains for tag/category filtering and in for matching a set of specific IDs
  • Use onSnapshot instead of getDocs when you need the UI to update as the underlying data changes
  • Keep the in operator array under 30 elements; split larger sets into batched queries
  • For complex querying needs beyond what Firestore supports natively, consider integrating Algolia or RapidDev to build a custom query layer

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Show me all Firestore where clause operators with examples: ==, !=, <, >, <=, >=, in, not-in, array-contains, and array-contains-any. Include a compound query with multiple where clauses, the orderBy and limit combination, and explain when a composite index is needed.

Firebase Prompt

Create a Firestore query helper that supports filtering products by category (equality), price range (min/max), and tags (array-contains). Use Firebase modular SDK v9+ with TypeScript. Include both one-time getDocs and real-time onSnapshot versions. Show the composite index definition needed.

Frequently asked questions

Can I use OR logic in Firestore where clauses?

Firestore added the or() function in SDK v9.18+. You can write or(where('status', '==', 'active'), where('status', '==', 'pending')). For older SDKs, run separate queries and merge results client-side.

How many where clauses can I chain in one query?

There is no hard limit on the number of equality where clauses. However, you can only have range filters on a single field per query (unless using composite indexes), one array-contains, and one in or not-in per query.

Why does my where query return empty results even though documents exist?

Check three things: (1) the field name in your where clause matches the exact field name in your documents, (2) the data type matches (string vs number), and (3) if you have security rules, they may be blocking the query even if the documents match.

Do where queries charge per document scanned or per document returned?

Firestore charges per document returned by the query, not per document scanned. However, a query that returns zero results still incurs a minimum of one read charge.

Can I filter by a field in a nested object?

Yes. Use dot notation in the field name: where('address.city', '==', 'New York'). This works for top-level maps. For arrays of objects, you cannot filter by fields inside array elements — restructure your data or use a subcollection.

What is the maximum number of values in an in clause?

The in and array-contains-any operators accept up to 30 values (increased from 10 in SDK updates). The not-in operator accepts up to 10 values.

Do I need a composite index for every compound query?

Not always. Queries with only equality filters on different fields use single-field indexes (created automatically). You need a composite index when combining equality with range on different fields, or when combining where with orderBy on a different field.

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.