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

How to Filter Documents by Array Contains in Firestore

To filter Firestore documents by array field values, use the where() function with the array-contains operator. This returns all documents where the specified array field contains the given value. For matching any of multiple values, use array-contains-any with an array of up to 10 values. You can combine array-contains with equality filters in compound queries, but you cannot use array-contains and array-contains-any in the same query, and you can only have one array-contains clause per query.

What you'll learn

  • How to use array-contains to filter documents by a single array value
  • How to use array-contains-any to match documents containing any of several values
  • What compound query limitations apply when using array operators
  • How to design array fields for efficient querying
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate8 min read10-15 minFirebase JS SDK v9+, Firestore (all plans)March 2026RapidDev Engineering Team
TL;DR

To filter Firestore documents by array field values, use the where() function with the array-contains operator. This returns all documents where the specified array field contains the given value. For matching any of multiple values, use array-contains-any with an array of up to 10 values. You can combine array-contains with equality filters in compound queries, but you cannot use array-contains and array-contains-any in the same query, and you can only have one array-contains clause per query.

Filtering Firestore Documents by Array Fields

Firestore provides two array-specific query operators: array-contains for checking if an array field includes a single value, and array-contains-any for matching against multiple possible values. These operators are powerful for tags, categories, roles, and other multi-value fields. This tutorial covers both operators, their limitations, and data modeling strategies that make array queries efficient.

Prerequisites

  • A Firebase project with Firestore enabled
  • Firebase JS SDK v9+ installed in your project
  • Firestore initialized with initializeApp and getFirestore
  • Documents with array fields to query against

Step-by-step guide

1

Query with array-contains for a single value

The array-contains operator checks whether an array field contains a specific value. It works with strings, numbers, and booleans inside the array. Import query, collection, where, and getDocs from firebase/firestore. Build the query with where('fieldName', 'array-contains', value) and execute it with getDocs. Firestore automatically indexes array fields for array-contains queries — no composite index is needed for single-operator queries.

typescript
1import { getFirestore, collection, query, where, getDocs } from 'firebase/firestore';
2
3const db = getFirestore();
4
5// Find all articles tagged with 'firebase'
6async function getArticlesByTag(tag: string) {
7 const q = query(
8 collection(db, 'articles'),
9 where('tags', 'array-contains', tag)
10 );
11
12 const snapshot = await getDocs(q);
13 return snapshot.docs.map((doc) => ({
14 id: doc.id,
15 ...doc.data()
16 }));
17}
18
19// Example: all articles with the 'firebase' tag
20const articles = await getArticlesByTag('firebase');

Expected result: Returns all documents where the tags array field includes the exact value 'firebase'.

2

Query with array-contains-any for multiple values

The array-contains-any operator returns documents where the array field contains at least one of the provided values. Pass an array of up to 10 values. This is useful for filtering by multiple tags or categories in a single query. If you need more than 10 values, split them into multiple queries and merge the results client-side.

typescript
1import { getFirestore, collection, query, where, getDocs } from 'firebase/firestore';
2
3const db = getFirestore();
4
5// Find articles tagged with ANY of these topics
6async function getArticlesByAnyTag(tags: string[]) {
7 const q = query(
8 collection(db, 'articles'),
9 where('tags', 'array-contains-any', tags)
10 );
11
12 const snapshot = await getDocs(q);
13 return snapshot.docs.map((doc) => ({
14 id: doc.id,
15 ...doc.data()
16 }));
17}
18
19// Returns articles tagged with 'firebase' OR 'react' OR 'typescript'
20const articles = await getArticlesByAnyTag(['firebase', 'react', 'typescript']);

Expected result: Returns all documents where the tags array contains at least one of the specified values.

3

Combine array-contains with other where clauses

You can combine array-contains with equality (==) and comparison (<, >, etc.) operators in the same query. However, compound queries with range operators and array-contains require a composite index. Firestore provides a direct link in the error message to create the required index when you first run the query.

typescript
1import { getFirestore, collection, query, where, orderBy, limit, getDocs } from 'firebase/firestore';
2
3const db = getFirestore();
4
5// Find published articles with the 'firebase' tag, ordered by date
6async function getPublishedArticlesByTag(tag: string) {
7 const q = query(
8 collection(db, 'articles'),
9 where('tags', 'array-contains', tag),
10 where('status', '==', 'published'),
11 orderBy('publishedAt', 'desc'),
12 limit(20)
13 );
14
15 const snapshot = await getDocs(q);
16 return snapshot.docs.map((doc) => ({
17 id: doc.id,
18 ...doc.data()
19 }));
20}

Expected result: Returns up to 20 published articles with the specified tag, ordered by publish date descending.

4

Understand the limitations of array queries

Firestore enforces strict rules on array operators: (1) You can only use one array-contains or array-contains-any per query. (2) You cannot combine array-contains-any with in or not-in in the same query. (3) array-contains matches the exact element — it cannot match partial strings or nested object fields within the array. (4) If you need to filter by ALL values (AND logic), array-contains only supports one value per query. For AND logic across multiple values, run separate queries and intersect the results, or restructure your data model.

typescript
1// These will NOT work:
2
3// ERROR: Cannot have two array-contains in one query
4const bad1 = query(
5 collection(db, 'articles'),
6 where('tags', 'array-contains', 'firebase'),
7 where('tags', 'array-contains', 'react')
8);
9
10// ERROR: Cannot combine array-contains-any with in
11const bad2 = query(
12 collection(db, 'articles'),
13 where('tags', 'array-contains-any', ['firebase', 'react']),
14 where('status', 'in', ['published', 'draft'])
15);
16
17// WORKAROUND for AND logic: use a map field instead
18// Store { firebase: true, react: true } and query with:
19const workaround = query(
20 collection(db, 'articles'),
21 where('tagMap.firebase', '==', true),
22 where('tagMap.react', '==', true)
23);

Expected result: You understand which query combinations are allowed and can choose the right data model for your filtering needs.

5

Use array-contains with real-time listeners

Array-contains queries work with onSnapshot for real-time updates just like any other Firestore query. When a document's array field changes (a tag is added or removed), the listener fires with the updated results. Use the same query construction and pass it to onSnapshot instead of getDocs.

typescript
1import { getFirestore, collection, query, where, onSnapshot } from 'firebase/firestore';
2
3const db = getFirestore();
4
5function listenToTaggedArticles(tag: string, callback: (articles: any[]) => void) {
6 const q = query(
7 collection(db, 'articles'),
8 where('tags', 'array-contains', tag)
9 );
10
11 const unsubscribe = onSnapshot(q, (snapshot) => {
12 const articles = snapshot.docs.map((doc) => ({
13 id: doc.id,
14 ...doc.data()
15 }));
16 callback(articles);
17 });
18
19 return unsubscribe;
20}
21
22// Start listening
23const unsub = listenToTaggedArticles('firebase', (articles) => {
24 console.log('Updated articles:', articles.length);
25});
26
27// Call unsub() to stop listening

Expected result: The callback fires initially with matching documents and again whenever a matching document is added, modified, or removed.

Complete working example

firestore-array-queries.ts
1import { initializeApp } from 'firebase/app';
2import {
3 getFirestore,
4 collection,
5 query,
6 where,
7 orderBy,
8 limit,
9 getDocs,
10 onSnapshot,
11 Unsubscribe
12} from 'firebase/firestore';
13
14const app = initializeApp({
15 // Your Firebase config
16});
17const db = getFirestore(app);
18
19interface Article {
20 id: string;
21 title: string;
22 tags: string[];
23 status: string;
24 publishedAt: Date;
25}
26
27// Find documents containing a specific tag
28export async function getByTag(tag: string): Promise<Article[]> {
29 const q = query(
30 collection(db, 'articles'),
31 where('tags', 'array-contains', tag)
32 );
33 const snap = await getDocs(q);
34 return snap.docs.map((d) => ({ id: d.id, ...d.data() } as Article));
35}
36
37// Find documents containing any of the given tags
38export async function getByAnyTag(tags: string[]): Promise<Article[]> {
39 const q = query(
40 collection(db, 'articles'),
41 where('tags', 'array-contains-any', tags.slice(0, 10))
42 );
43 const snap = await getDocs(q);
44 return snap.docs.map((d) => ({ id: d.id, ...d.data() } as Article));
45}
46
47// Compound query: tag + status filter + ordering
48export async function getPublishedByTag(
49 tag: string,
50 max: number = 20
51): Promise<Article[]> {
52 const q = query(
53 collection(db, 'articles'),
54 where('tags', 'array-contains', tag),
55 where('status', '==', 'published'),
56 orderBy('publishedAt', 'desc'),
57 limit(max)
58 );
59 const snap = await getDocs(q);
60 return snap.docs.map((d) => ({ id: d.id, ...d.data() } as Article));
61}
62
63// Real-time listener for tagged articles
64export function onTaggedArticles(
65 tag: string,
66 callback: (articles: Article[]) => void
67): Unsubscribe {
68 const q = query(
69 collection(db, 'articles'),
70 where('tags', 'array-contains', tag)
71 );
72 return onSnapshot(q, (snap) => {
73 callback(snap.docs.map((d) => ({ id: d.id, ...d.data() } as Article)));
74 });
75}

Common mistakes when filtering Documents by Array Contains in Firestore

Why it's a problem: Using two array-contains clauses in the same query for AND logic

How to avoid: Firestore allows only one array-contains per query. For AND logic, use a map field instead (e.g., tagMap: { firebase: true, react: true }) and query with multiple equality filters.

Why it's a problem: Passing more than 10 values to array-contains-any

How to avoid: The limit is 10 values per query. Split larger sets into batches of 10, run separate queries, and merge results while deduplicating by document ID.

Why it's a problem: Expecting array-contains to match partial strings or substrings

How to avoid: array-contains performs exact element matching. Store values in a consistent format. For substring search, consider maintaining a searchable keywords array or using an external search service.

Why it's a problem: Not creating composite indexes for compound queries with array-contains

How to avoid: When combining array-contains with orderBy or range filters, Firestore requires a composite index. Click the error link in the console to create it, or add it to firestore.indexes.json.

Best practices

  • Store array values in a consistent format (lowercase strings) for reliable array-contains matching
  • Use array-contains-any instead of multiple queries when you need OR logic across tags
  • Limit arrays to a reasonable size — very large arrays (1000+ elements) increase document read costs
  • Consider a map field instead of an array when you need AND logic across multiple values
  • Create composite indexes proactively in firestore.indexes.json for known compound queries
  • Use arrayUnion and arrayRemove for atomic array modifications instead of reading and rewriting the entire array
  • Combine array-contains with pagination (limit + startAfter) for large result sets

Still stuck?

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

ChatGPT Prompt

Show me how to filter Firestore documents using array-contains and array-contains-any in the modular SDK v9+. Include examples for single tag filtering, multi-tag OR filtering, compound queries with other where clauses, and a real-time listener. Explain the query limitations and data modeling alternatives for AND logic.

Firebase Prompt

Write TypeScript utility functions for Firestore array queries: getByTag using array-contains, getByAnyTag using array-contains-any (handling the 10-value limit), a compound query combining array-contains with equality filter and orderBy, and a real-time listener. Include the Article interface and proper typing.

Frequently asked questions

Can I use array-contains to check for objects inside an array?

Yes, but the object must match exactly — all fields and values must be identical. You cannot match partial objects or query individual fields within array objects. For complex array element queries, consider restructuring as a subcollection.

How many elements can a Firestore array field contain?

There is no explicit array element limit, but the entire document must be under 1 MiB and 20,000 fields. In practice, keep arrays under a few hundred elements for performance. Very large arrays increase read costs and index size.

Can I do a NOT array-contains query?

No. Firestore does not support a negated array-contains operator. As a workaround, query without the array filter and filter out unwanted results client-side, or restructure your data to use a different querying strategy.

Does array-contains work with the Firestore Emulator?

Yes. The Firestore Emulator fully supports array-contains and array-contains-any queries with the same behavior as production, including composite index requirements.

What is the difference between array-contains and in?

array-contains checks if a document's array field includes a specific value. 'in' checks if a document's scalar field matches any value in a provided list. They operate on different field types.

Can RapidDev help design Firestore data models for complex filtering requirements?

Yes, RapidDev can analyze your query patterns and design optimal Firestore data models using arrays, maps, subcollections, and denormalization to support efficient filtering without excessive read costs.

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.