Firestore
Common use cases
Core
Initialization
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const firebaseApp = initializeApp({ /* config */});
const db = getFirestore(firebaseApp);
Connect to the Emulator Suite
import { getFirestore, useFirestoreEmulator } from "firebase/firestore";
// firebaseApps previously initialized using initializeApp()
const db = getFirestore();
useFirestoreEmulator(db, 'localhost', 8080);
Disable network
import { disableNetwork } from "firebase/firestore"; 
await disableNetwork(db);
console.log("Network disabled!");
// Do offline actions
// [START_EXCLUDE]
console.log("Network disabled!");
// [END_EXCLUDE]
Enable offline (IndexedDB persistence)
import { enableIndexedDbPersistence } from "firebase/firestore"; 
enableIndexedDbPersistence(db)
  .catch((err) => {
      if (err.code == 'failed-precondition') {
          // Multiple tabs open, persistence can only be enabled
          // in one tab at a a time.
          // ...
      } else if (err.code == 'unimplemented') {
          // The current browser does not support all of the
          // features required to enable persistence
          // ...
      }
  });
// Subsequent queries will use persistence, if it was enabled successfully
Realtime listeners
Listen to a document
import { doc, onSnapshot } from "firebase/firestore";
const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => {
    console.log("Current data: ", doc.data());
});
Query a collection
import { collection, query, where, onSnapshot } from "firebase/firestore";
const q = query(collection(db, "cities"), where("state", "==", "CA"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
  const cities = [];
  querySnapshot.forEach((doc) => {
      cities.push(doc.data().name);
  });
  console.log("Current cities in CA: ", cities.join(", "));
});
Range query
import { collection, where, query, onSnapshot } from "firebase/firestore"; 
const q = query(collection(db, "users"), where("born", "<", 1900));
const unsubscribe = onSnapshot(q, (snapshot) => {
    console.log("Current users born before 1900:");
    snapshot.forEach((userSnapshot) => {
        console.log(userSnapshot.data());
    });
});
Group by query
import { collectionGroup, query, where, getDocs } from "firebase/firestore";  
const museums = query(collectionGroup(db, 'landmarks'), where('type', '==', 'museum'));
const querySnapshot = await getDocs(museums);
querySnapshot.forEach((doc) => {
    console.log(doc.id, ' => ', doc.data());
});
In filter
import { query, where } from "firebase/firestore";
const q = query(citiesRef, where('country', 'in', ['USA', 'Japan']));
Not in filter
import { query, where } from "firebase/firestore";
const q = query(citiesRef, where('country', 'not-in', ['USA', 'Japan']));
Listen for errors
import { collection, onSnapshot } from "firebase/firestore";
const unsubscribe = onSnapshot(
  collection(db, "cities"), 
  (snapshot) => {
    // ...
  },
  (error) => {
    // ...
  });
Detach listener
import { collection, onSnapshot } from "firebase/firestore";
const unsubscribe = onSnapshot(collection(db, "cities"), () => {
  // Respond to data
  // ...
});
// Later ...
// Stop listening to changes
unsubscribe();
Include cache updates
import { collection, onSnapshot, where, query } from "firebase/firestore"; 
const q = query(collection(db, "cities"), where("state", "==", "CA"));
onSnapshot(q, { includeMetadataChanges: true }, (snapshot) => {
    snapshot.docChanges().forEach((change) => {
        if (change.type === "added") {
            console.log("New city: ", change.doc.data());
        }
        const source = snapshot.metadata.fromCache ? "local cache" : "server";
        console.log("Data came from " + source);
    });
});
Listen for local updates
import { doc, onSnapshot } from "firebase/firestore";
const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => {
  const source = doc.metadata.hasPendingWrites ? "Local" : "Server";
  console.log(source, " data: ", doc.data());
});
CRUD operations
Get a document
import { doc, getDoc } from "firebase/firestore";
const docRef = doc(db, "cities", "SF");
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
  console.log("Document data:", docSnap.data());
} else {
  // doc.data() will be undefined in this case
  console.log("No such document!");
}
Get a collection
import { collection, getDocs } from "firebase/firestore"; 
const querySnapshot = await getDocs(collection(db, "users"));
querySnapshot.forEach((doc) => {
  console.log(`${doc.id} => ${doc.data()}`);
});
Get a customer object (Converter)
import { doc, getDoc} from "firebase/firestore"; 
const ref = doc(db, "cities", "LA").withConverter(cityConverter);
const docSnap = await getDoc(ref);
if (docSnap.exists()) {
  // Convert to City object
  const city = docSnap.data();
  // Use a City instance method
  console.log(city.toString());
} else {
  console.log("No such document!");
}
Query a collection
import { collection, query, where, getDocs } from "firebase/firestore";
const q = query(collection(db, "cities"), where("capital", "==", true));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
  // doc.data() is never undefined for query doc snapshots
  console.log(doc.id, " => ", doc.data());
});
Query a collection with multiple clauses
// Will return all Springfields
import { collection, query, orderBy, startAt } from "firebase/firestore";  
const q1 = query(collection(db, "cities"),
   orderBy("name"),
   orderBy("state"),
   startAt("Springfield"));
// Will return "Springfield, Missouri" and "Springfield, Wisconsin"
const q2 = query(collection(db, "cities"),
   orderBy("name"),
   orderBy("state"),
   startAt("Springfield", "Missouri"));
Collection group query (Query across collections)
import { collectionGroup, query, where, getDocs } from "firebase/firestore";  
const museums = query(collectionGroup(db, 'landmarks'), where('type', '==', 'museum'));
const querySnapshot = await getDocs(museums);
querySnapshot.forEach((doc) => {
    console.log(doc.id, ' => ', doc.data());
});
Set a document
import { collection, doc, setDoc } from "firebase/firestore"; 
// Add a new document with a generated id
const newCityRef = doc(collection(db, "cities"));
// later...
await setDoc(newCityRef, data);
Set a document with merge
import { doc, setDoc } from "firebase/firestore"; 
const cityRef = doc(db, 'cities', 'BJ');
setDoc(cityRef, { capital: true }, { merge: true });
Update a document
import { doc, updateDoc } from "firebase/firestore";
const washingtonRef = doc(db, "cities", "DC");
// Set the "capital" field of the city 'DC'
await updateDoc(washingtonRef, {
  capital: true
});
Update a document with an array
import { doc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore";
const washingtonRef = doc(db, "cities", "DC");
// Atomically add a new region to the "regions" array field.
await updateDoc(washingtonRef, {
    regions: arrayUnion("greater_virginia")
});
// Atomically remove a region from the "regions" array field.
await updateDoc(washingtonRef, {
    regions: arrayRemove("east_coast")
});
Update a document with an increment
import { doc, updateDoc, increment } from "firebase/firestore";
const washingtonRef = doc(db, "cities", "DC");
// Atomically increment the population of the city by 50.
await updateDoc(washingtonRef, {
    population: increment(50)
});
Update a document with a nested object
import { doc, setDoc, updateDoc } from "firebase/firestore"; 
// Create an initial document to update.
const frankDocRef = doc(db, "users", "frank");
await setDoc(frankDocRef, {
    name: "Frank",
    favorites: { food: "Pizza", color: "Blue", subject: "recess" },
    age: 12
});
// To update age and favorite color:
await updateDoc(frankDocRef, {
    "age": 13,
    "favorites.color": "Red"
});
Update a document with a server timestamp
import { updateDoc, serverTimestamp } from "firebase/firestore";
const docRef = doc(db, 'objects', 'some-id');
// Update the timestamp field with the value from the server
const updateTimestamp = await updateDoc(docRef, {
    timestamp: serverTimestamp()
});
Set a document with a data types
import { doc, setDoc, Timestamp } from "firebase/firestore"; 
const docData = {
    stringExample: "Hello world!",
    booleanExample: true,
    numberExample: 3.14159265,
    dateExample: Timestamp.fromDate(new Date("December 10, 1815")),
    arrayExample: [5, true, "hello"],
    nullExample: null,
    objectExample: {
        a: 5,
        b: {
            nested: "foo"
        }
    }
};
await setDoc(doc(db, "data", "one"), docData);
Delete a document
import { doc, deleteDoc } from "firebase/firestore";
await deleteDoc(doc(db, "cities", "DC"));
Delete a collection
/**
 * Delete a collection, in batches of batchSize. Note that this does
 * not recursively delete subcollections of documents in the collection
 */
import { collection, query, orderBy, limit, getDocs, writeBatch } from "firebase/firestore"; 
function deleteCollection(db, collectionRef, batchSize) {
  const q = query(collectionRef, orderBy('__name__'), limit(batchSize));
  return new Promise((resolve) => {
      deleteQueryBatch(db, q, batchSize, resolve);
  });
}
async function deleteQueryBatch(db, query, batchSize, resolve) {
  const snapshot = await getDocs(query);
  // When there are no documents left, we are done
  let numDeleted = 0;
  if (snapshot.size > 0) {
    // Delete documents in a batch
    const batch = writeBatch(db);
    snapshot.docs.forEach((doc) => {
      batch.delete(doc.ref);
    });
    await batch.commit();
    numDeleted = snapshot.size;
  }
  if (numDeleted < batchSize) {
    resolve();
    return;
  }
  // Recurse on the next process tick, to avoid
  // exploding the stack.
  setTimeout(() => {
      deleteQueryBatch(db, query, batchSize, resolve);
  }, 0);
}
Delete a field
import { doc, updateDoc, deleteField } from "firebase/firestore";
const cityRef = doc(db, 'cities', 'BJ');
// Remove the 'capital' field from the document
await updateDoc(cityRef, {
    capital: deleteField()
});
Run a transaction
import { runTransaction } from "firebase/firestore";
try {
  await runTransaction(db, async (transaction) => {
    const sfDoc = await transaction.get(sfDocRef);
    if (!sfDoc.exists()) {
      throw "Document does not exist!";
    }
    const newPopulation = sfDoc.data().population + 1;
    transaction.update(sfDocRef, { population: newPopulation });
  });
  console.log("Transaction successfully committed!");
} catch (e) {
  console.log("Transaction failed: ", e);
}
Advanced
Pagination
import { collection, query, orderBy, startAfter, limit, getDocs } from "firebase/firestore";  
// Query the first page of docs
const first = query(collection(db, "cities"), orderBy("population"), limit(25));
const documentSnapshots = await getDocs(first);
// Get the last visible document
const lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
const next = query(collection(db, "cities"),
    orderBy("population"),
    startAfter(lastVisible),
    limit(25));
Advanced Transactions
import { collection, doc, runTransaction } from "firebase/firestore";  
async function addRating(restaurantRef, rating) {
    // Create a reference for a new rating, for use inside the transaction
    const ratingRef = doc(collection(restaurantRef, 'ratings'));
    // In a transaction, add the new rating and update the aggregate totals
    await runTransaction(db, async (transaction) => {
        const res = await transaction.get(restaurantRef);
        if (!res.exists()) {
            throw "Document does not exist!";
        }
        // Compute new number of ratings
        const newNumRatings = res.data().numRatings + 1;
        // Compute new average rating
        const oldRatingTotal = res.data().avgRating * res.data().numRatings;
        const newAvgRating = (oldRatingTotal + rating) / newNumRatings;
        // Commit to Firestore
        transaction.update(restaurantRef, {
            numRatings: newNumRatings,
            avgRating: newAvgRating
        });
        transaction.set(ratingRef, { rating: rating });
    });
}
Geoquery — Add hash
import { doc, updateDoc } from 'firebase/firestore';
// Compute the GeoHash for a lat/lng point
const lat = 51.5074;
const lng = 0.1278;
const hash = geofire.geohashForLocation([lat, lng]);
// Add the hash and the lat/lng to the document. We will use the hash
// for queries and the lat/lng for distance comparisons.
const londonRef = doc(db, 'cities', 'LON');
await updateDoc(londonRef, {
  geohash: hash,
  lat: lat,
  lng: lng
});
Geoquery — Query hashes
import { collection, query, orderBy, startAt, endAt, getDocs } from 'firebase/firestore';
// Find cities within 50km of London
const center = [51.5074, 0.1278];
const radiusInM = 50 * 1000;
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
// a separate query for each pair. There can be up to 9 pairs of bounds
// depending on overlap, but in most cases there are 4.
const bounds = geofire.geohashQueryBounds(center, radiusInM);
const promises = [];
for (const b of bounds) {
  const q = query(
    collection(db, 'cities'), 
    orderBy('geohash'), 
    startAt(b[0]), 
    endAt(b[1]));
  promises.push(getDocs(q));
}
// Collect all the query results together into a single list
const snapshots = await Promise.all(promises);
const matchingDocs = [];
for (const snap of snapshots) {
  for (const doc of snap.docs) {
    const lat = doc.get('lat');
    const lng = doc.get('lng');
    // We have to filter out a few false positives due to GeoHash
    // accuracy, but most will match
    const distanceInKm = geofire.distanceBetween([lat, lng], center);
    const distanceInM = distanceInKm * 1000;
    if (distanceInM <= radiusInM) {
      matchingDocs.push(doc);
    }
  }
}