DS DevShelfHub Projects · AI tools
Cheatsheets / Firebase
Cheatsheet · Dev tooling

Firebase Cheatsheet: Firestore, Auth, Functions and FCM

By DevShelfHub

Firestore CRUD + queries + listeners, security rules, auth (email/OAuth/anonymous), Storage, Cloud Functions gen 2, Admin SDK, FCM, emulator suite.

111 items 10 min Firestore Auth Functions

Start hereQuick start · 6 you’ll reach for daily

CLI initfirebase init
Local stackfirebase emulators:start
Init SDKinitializeApp(config)
Add docaddDoc(collection(db,'notes'),{...})
Sign insignInWithEmailAndPassword(...)
ListenonSnapshot(query(...), cb)

Target versions · paceVersions

Targets: firebase (JS SDK) ≥ 10.12 firebase-admin ≥ 12 firebase-functions ≥ 5 (gen2) firebase-tools ≥ 13

The modern JS SDK is modular and tree-shakable — always import from firebase/firestore, firebase/auth, etc., not the deprecated compat namespace. Cloud Functions gen 2 runs on Cloud Run under the hood: cold-starts are quicker, concurrency is configurable, and secrets are first-class. The Realtime Database still exists, but Firestore is the default for new projects.

CLI · emulators · SDKSetup

bash
# CLI
npm install -g firebase-tools
firebase --version
firebase login

# Pick products inside the wizard:
#   firestore, functions, hosting, storage, emulators
firebase init

# Local stack — auth, firestore, functions, storage, hosting on one box
firebase emulators:start --import=./seed --export-on-exit=./seed
# Emulator UI: http://localhost:4000

# Web SDK (modular, tree-shakable)
npm i firebase

# Initialise once per app
cat > src/firebase.ts <<'TS'
import { initializeApp }    from 'firebase/app'
import { getAuth,         connectAuthEmulator }      from 'firebase/auth'
import { getFirestore,    connectFirestoreEmulator } from 'firebase/firestore'
import { getStorage,      connectStorageEmulator }   from 'firebase/storage'

const app = initializeApp({
    apiKey:            import.meta.env.VITE_FB_API_KEY,
    authDomain:        import.meta.env.VITE_FB_AUTH_DOMAIN,
    projectId:         import.meta.env.VITE_FB_PROJECT_ID,
    storageBucket:     import.meta.env.VITE_FB_STORAGE_BUCKET,
    appId:             import.meta.env.VITE_FB_APP_ID,
})
export const auth    = getAuth(app)
export const db      = getFirestore(app)
export const storage = getStorage(app)

if (import.meta.env.DEV) {
    connectAuthEmulator(auth, 'http://localhost:9099')
    connectFirestoreEmulator(db, 'localhost', 8080)
    connectStorageEmulator(storage, 'localhost', 9199)
}
TS

firebase ...Firebase CLI

npm install -g firebase-toolsInstall. Self-updating on use.
firebase loginOAuth in the browser; stores creds in ~/.config/configstore.
firebase initInteractive wizard — pick products: firestore, functions, hosting, storage, emulators.
firebase use --addAdd a project alias (e.g. dev / prod).
firebase emulators:start --import=./seed --export-on-exit=./seedLocal stack with deterministic seed data. UI on :4000.
firebase deploy --only firestore:rules,firestore:indexesDeploy just the rules + indexes — safer than a full deploy.
firebase deploy --only functions:api,functions:onNewNoteSelective function deploy.
firebase functions:log [--only api]Stream logs.
firebase firestore:delete /col --recursiveBulk delete a collection / subtree.
firebase apps:create web my-appProvision a Web app + print the config object.

Documents & collectionsFirestore basics

import { getFirestore, collection, doc, addDoc, setDoc, getDoc, updateDoc, deleteDoc, serverTimestamp } from 'firebase/firestore'Modular imports.
const db = getFirestore(app)One Firestore instance per app.
addDoc(collection(db, 'notes'), { ... })Server-assigned id. Returns a DocumentReference.
setDoc(doc(db,'notes', id), {...}, { merge: true })Upsert. merge: true preserves omitted fields.
getDoc(doc(db,'notes', id))One doc → DocumentSnapshot.
snap.exists() · snap.id · snap.data()Snapshot inspection. data() is undefined when absent.
getDocs(collection(db, 'notes'))Many docs → QuerySnapshot.
qs.docs.map(d => ({ id: d.id, ...d.data() }))Common “flatten with id” idiom.
updateDoc(doc(db,'notes', id), { done: true, updated: serverTimestamp() })Partial update. Errors if doc missing.
deleteDoc(doc(db,'notes', id))Hard delete.
serverTimestamp()Resolved server-side at write. Use for created/updated columns.
writeBatch(db).set(...).update(...).delete(...).commit()Atomic batch — up to 500 writes.
runTransaction(db, async (tx) => { ... })Optimistic txn with retries on conflict.
collection(db, 'users', uid, 'notes')Subcollection path.
javascript
import { db, auth } from './firebase'
import {
    collection, doc, addDoc, setDoc, getDoc, getDocs,
    updateDoc, deleteDoc, query, where, orderBy, limit,
    onSnapshot, serverTimestamp, writeBatch,
} from 'firebase/firestore'

const uid = auth.currentUser!.uid

// 1. Create — server-assigned ID
const ref = await addDoc(collection(db, 'notes'), {
    title:   'Ship firebase cheatsheet',
    owner:   uid,
    done:    false,
    created: serverTimestamp(),                  // resolves on the server
})

// 2. Upsert with merge — safe re-create
await setDoc(doc(db, 'notes', ref.id),
    { tags: ['draft'], updated: serverTimestamp() },
    { merge: true })

// 3. Read one
const snap = await getDoc(doc(db, 'notes', ref.id))
if (snap.exists()) console.log(snap.id, snap.data())

// 4. Query + live updates
const q = query(
    collection(db, 'notes'),
    where('owner', '==', uid),
    where('done',  '==', false),
    orderBy('created', 'desc'),
    limit(20),
)
const unsub = onSnapshot(q, (qs) => {
    qs.docChanges().forEach((ch) => {
        console.log(ch.type, ch.doc.id, ch.doc.data())  // added | modified | removed
    })
})

// 5. Atomic batch (up to 500 ops)
const batch = writeBatch(db)
batch.update(doc(db, 'notes', ref.id), { done: true })
batch.delete(doc(db, 'notes', 'stale-id'))
await batch.commit()

// later: unsub() in your effect cleanup

where · orderBy · cursorsFirestore queries

query(collection(db,'notes'), where('owner','==',uid), orderBy('created','desc'), limit(20))Standard composed query.
where('priority', 'in', [1,2,3])in supports up to 30 values.
where('tags', 'array-contains', 'urgent')Array membership.
where('tags', 'array-contains-any', ['a','b'])Any of several elements.
where('created', '>=', threshold)Range operators: <, <=, >, >=, !=.
orderBy('created', 'desc')First range field must also be the first orderBy.
startAfter(lastDoc) · startAt(value) · endBefore(...)Cursor pagination; pass the last DocumentSnapshot.
firestore.indexes.jsonComposite indexes live here. Error messages link to the index that’s missing.
collectionGroup(db, 'notes')Query a subcollection name across the whole DB.
await getCountFromServer(query(...))Server-side count without reading any doc bodies.
getAggregateFromServer(q, { total: sum('amount'), avg: average('amount') })Sum / average / count in one call.

onSnapshot · docChangesReal-time listeners

const unsub = onSnapshot(query(...), snap => ...)Subscribe. Returns a cleanup function.
unsub()Always call it — orphan listeners burn quota.
snap.docChanges().forEach(c => c.type)added / modified / removed — ideal for diffing UI lists.
snap.metadata.hasPendingWritestrue if this is a local optimistic write.
snap.metadata.fromCachetrue if data is from offline cache, not the server.
onSnapshot(q, { includeMetadataChanges: true }, cb)Fire on metadata-only changes too.

Per-request authorisationSecurity rules

service cloud.firestore { match /databases/{db}/documents { ... } }Top-level wrapper. Required.
match /notes/{noteId} { ... }Per-collection block.
allow read: if request.auth != nullSign-in gate.
allow create: if request.auth.uid == request.resource.data.ownerrequest.resource = incoming doc.
allow update: if resource.data.owner == request.auth.uidresource = existing doc.
allow update: if request.resource.data.owner == resource.data.ownerImmutability check — field can’t be changed even by the owner.
match /users/{uid}/{document=**} { ... }Wildcard subtree match.
function isOwner() { return request.auth.uid == resource.data.owner; }Reusable predicate.
exists(/databases/$(db)/documents/admins/$(request.auth.uid))Cross-doc existence check — admin gate pattern.
get(/databases/$(db)/documents/users/$(uid)).data.role == 'admin'Cross-doc field read.
request.time · request.resource.data · resource.dataBuilt-in vars available in every rule.
match /{document=**} { allow read, write: if false; }Default-deny safety net at the bottom.
javascript
// firestore.rules — owner-only access pattern with a small admin helper.
rules_version = '2';

service cloud.firestore {
    match /databases/{database}/documents {

        // Helpers (reusable predicates)
        function isSignedIn() { return request.auth != null; }
        function isOwner()    { return request.auth.uid == resource.data.owner; }
        function becomesOwn() { return request.auth.uid == request.resource.data.owner; }
        function isAdmin()    {
            return exists(/databases/$(database)/documents/admins/$(request.auth.uid));
        }

        // Per-user private notes
        match /notes/{noteId} {
            allow read:   if isSignedIn() && (isOwner() || isAdmin());
            allow create: if isSignedIn() && becomesOwn()
                          && request.resource.data.created == request.time;
            allow update: if isOwner()
                          && request.resource.data.owner == resource.data.owner;   // owner immutable
            allow delete: if isOwner();
        }

        // Wildcard subtree — every doc under /users//...
        match /users/{uid}/{document=**} {
            allow read, write: if isSignedIn() && request.auth.uid == uid;
        }

        // Default deny
        match /{document=**} {
            allow read, write: if false;
        }
    }
}

Email · OAuth · anonymousAuthentication

getAuth(app)One Auth instance per app.
await createUserWithEmailAndPassword(auth, email, pwd)Sign-up.
await signInWithEmailAndPassword(auth, email, pwd)Sign-in.
await signInWithPopup(auth, new GoogleAuthProvider())OAuth popup — desktop-friendly.
await signInWithRedirect(auth, provider) · getRedirectResult(auth)Mobile-safer flow. Call getRedirectResult on the callback page.
await signInAnonymously(auth)Guest user. Can be linked to a real account later.
await sendPasswordResetEmail(auth, email)Email-based reset link.
await updateProfile(auth.currentUser, { displayName, photoURL })Set display fields.
await updateEmail(user, newEmail) · updatePassword(user, newPwd)Both require a recent sign-in.
onAuthStateChanged(auth, user => ...)Top-level sign-in state subscription.
await user.getIdToken(/* forceRefresh */ true)Get a fresh JWT to send to your backend.
await signOut(auth)Clears the local session.
javascript
import { auth } from './firebase'
import {
    createUserWithEmailAndPassword, signInWithEmailAndPassword,
    signInWithPopup, signInWithRedirect, getRedirectResult,
    signInAnonymously, sendPasswordResetEmail, updateProfile,
    onAuthStateChanged, signOut,
    GoogleAuthProvider, GithubAuthProvider,
} from 'firebase/auth'

// 1. Email + password
await createUserWithEmailAndPassword(auth, 'ada@example.com', 'correct horse battery')
const cred = await signInWithEmailAndPassword(auth, 'ada@example.com', 'correct horse battery')

// 2. OAuth — popup vs redirect (mobile-safer)
await signInWithPopup(auth, new GoogleAuthProvider())
// or:
await signInWithRedirect(auth, new GithubAuthProvider())
const after = await getRedirectResult(auth)        // call on the callback page

// 3. Anonymous (great for "try before sign up")
await signInAnonymously(auth)

// 4. Profile + password resets
await updateProfile(auth.currentUser!, { displayName: 'Ada Lovelace' })
await sendPasswordResetEmail(auth, 'ada@example.com')

// 5. React to sign-in state across the whole app
const unsub = onAuthStateChanged(auth, async (user) => {
    if (!user) return navigate('/login')
    const token = await user.getIdToken()          // JWT for your backend
    setHeader('Authorization', `Bearer ${token}`)
})

// later: await signOut(auth); unsub();

Files in Cloud StorageStorage

import { getStorage, ref, uploadBytes, uploadBytesResumable, getDownloadURL, deleteObject } from 'firebase/storage'Modular imports.
const storage = getStorage(app)One storage handle per app.
const r = ref(storage, 'avatars/' + uid)Build a reference. Slash-separated path.
await uploadBytes(r, file)One-shot upload.
uploadBytesResumable(r, file).on('state_changed', snap => snap.bytesTransferred / snap.totalBytes)Resumable upload with progress.
await getDownloadURL(r)Token-signed URL — works regardless of bucket visibility.
await deleteObject(r)Hard delete.
storage.rules: match /avatars/{uid} { allow read: if true; allow write: if request.auth.uid == uid }Storage has its own rules language — mirror Firestore patterns.

Gen 2 · HTTPS · triggers · cronCloud Functions

import { onRequest, onCall } from 'firebase-functions/v2/https'HTTPS endpoints + callable.
import { onDocumentCreated, onDocumentWritten } from 'firebase-functions/v2/firestore'Firestore triggers.
import { onSchedule } from 'firebase-functions/v2/scheduler'Cron-style schedules.
export const api = onRequest({ region, cors }, (req, res) => ...)Plain HTTPS handler. cors: true enables wildcard CORS.
export const hello = onCall((req) => ({ uid: req.auth?.uid }))Callable — auth context resolved server-side.
export const onNew = onDocumentCreated('notes/{id}', async (event) => ...)Doc trigger — event.data is the new snapshot.
export const nightly = onSchedule('every day 02:00', async () => ...)Cloud Scheduler under the hood. Cron syntax also accepted.
defineSecret('OPENAI_KEY')Declare a secret. Bind via { secrets: [...] } on the function.
throw new HttpsError('permission-denied', 'reason')Typed error from onCall — surfaces cleanly in the client SDK.
javascript
// Cloud Functions for Firebase — Gen 2 (Cloud Run under the hood).
import { onRequest, onCall, HttpsError } from 'firebase-functions/v2/https'
import { onDocumentCreated }              from 'firebase-functions/v2/firestore'
import { onSchedule }                     from 'firebase-functions/v2/scheduler'
import { defineSecret }                   from 'firebase-functions/params'
import { initializeApp }                  from 'firebase-admin/app'
import { getFirestore }                   from 'firebase-admin/firestore'

initializeApp()
const OPENAI = defineSecret('OPENAI_KEY')           // bound at deploy

// 1. HTTPS endpoint
export const api = onRequest(
    { region: 'us-central1', cors: true, secrets: [OPENAI] },
    async (req, res) => {
        res.json({ ok: true, key_prefix: OPENAI.value().slice(0, 5) })
    },
)

// 2. Callable — auth context resolved server-side
export const sayHello = onCall(async (req) => {
    if (!req.auth) throw new HttpsError('unauthenticated', 'sign in first')
    return { hello: req.auth.uid }
})

// 3. Firestore trigger — fires on every new /notes doc
export const onNewNote = onDocumentCreated(
    { document: 'notes/{noteId}', region: 'us-central1' },
    async (event) => {
        const before = event.data?.data()
        await event.data?.ref.update({ ingestedAt: new Date() })
    },
)

// 4. Scheduled — cron syntax or "every day 02:00"
export const nightly = onSchedule('every day 02:00', async () => {
    const stale = await getFirestore().collection('notes')
        .where('done', '==', true).get()
    await Promise.all(stale.docs.map((d) => d.ref.delete()))
})

When to pick whichRealtime DB vs Firestore

Realtime DatabaseSingle JSON tree. Deep onValue listeners. Pricing: bandwidth + storage.
FirestoreDocument model. Indexed queries. Scales horizontally. Pricing: per-op + storage + bandwidth.
Prefer RTDB whenVery-low-latency presence, ephemeral counters, <100k connected clients per shard.
Prefer Firestore whenAnything else, especially anything multi-region or with complex queries.
import { getDatabase, ref, onValue, set } from 'firebase/database'RTDB modular imports if you need it.

Server-side · bypasses rulesAdmin SDK

import { initializeApp, cert } from 'firebase-admin/app'Admin entry point.
initializeApp({ credential: cert(serviceAccount) })Use a service-account JSON. Never ship to the browser.
await getAuth().setCustomUserClaims(uid, { role: 'admin' })Embed claims in the JWT — available in rules as request.auth.token.role.
await getAuth().verifyIdToken(token)Validate an incoming JWT in your backend.
await getAuth().listUsers(1000, pageToken)Paged user listing.
await getFirestore().collection('notes').doc(id).delete()Bypasses Firestore rules — for cleanup jobs and admin tooling only.
getFirestore().bulkWriter()High-throughput batched writes with auto-retry.

Push notificationsCloud Messaging

getMessaging() · getToken(messaging, { vapidKey })Client: subscribe this device. Token = the push address.
onMessage(messaging, payload => ...)Foreground messages — service worker handles background.
getMessaging().send({ token, notification: { title, body } })Server: send to one device.
getMessaging().subscribeToTopic(tokens, 'newsletter')Topic-based fan-out.
getMessaging().send({ topic: 'newsletter', notification: {...} })Server-side broadcast.

Local dev stackEmulator suite

firebase init emulatorsPick services + ports.
firebase emulators:start --import=./seed --export-on-exit=./seedSeed in, snapshot out — deterministic local dev.
connectFirestoreEmulator(db, 'localhost', 8080)Point the JS SDK at local Firestore.
connectAuthEmulator(auth, 'http://localhost:9099')Local auth — no real users created.
connectStorageEmulator(storage, 'localhost', 9199)Local storage.
UI on http://localhost:4000Browse data, test rules, replay function calls.

Full pipeline · ~30 linesEnd-to-end · Auth-gated notes

Signs in, inserts a note with owner = auth.uid, subscribes to that user’s notes only (security rules enforce the boundary), updates a row, cleans up the listener and signs out.

javascript
// End-to-end: auth-gated notes app in ~30 lines.
// RLS-equivalent enforcement lives in firestore.rules (request.auth.uid == owner).
import { auth, db } from './firebase'
import {
    signInWithEmailAndPassword, onAuthStateChanged, signOut,
} from 'firebase/auth'
import {
    collection, addDoc, query, where, orderBy, onSnapshot,
    updateDoc, doc, serverTimestamp,
} from 'firebase/firestore'

async function main() {
    // 1. Sign in
    await signInWithEmailAndPassword(auth, 'ada@example.com', '****')

    onAuthStateChanged(auth, async (user) => {
        if (!user) return console.log('signed out')

        // 2. Insert — owner field must equal auth.uid (rules enforce this)
        const ref = await addDoc(collection(db, 'notes'), {
            title:   'Ship firebase cheatsheet',
            owner:   user.uid,
            done:    false,
            created: serverTimestamp(),
        })

        // 3. Subscribe to my own notes only
        const q = query(
            collection(db, 'notes'),
            where('owner', '==', user.uid),
            orderBy('created', 'desc'),
        )
        const unsub = onSnapshot(q, (qs) =>
            qs.docChanges().forEach((c) => console.log(c.type, c.doc.id)))

        // 4. Complete the note
        await updateDoc(doc(db, 'notes', ref.id), { done: true })

        // 5. Tidy up
        unsub()
        await signOut(auth)
    })
}

main().catch(console.error)

Best practiceGood to know

Write security rules as if everything is hostile. The JS SDK runs in the user’s browser; anything they can call, they can call with any arguments. request.auth.uid == resource.data.owner isn’t paranoid — it’s the minimum.
Always clean up onSnapshot listeners. Forgotten listeners survive component unmount, burn read quota, and keep stale data flowing into your state. Wire unsub() into useEffect’s cleanup.
Use the emulator suite as your dev DB. firebase emulators:start --import=./seed --export-on-exit=./seed gives you deterministic local data, free reads/writes, and an offline-first dev loop. Tests get the same setup — no separate mock infrastructure.

Common trapsWatch out for

Don’t store anything sensitive in client-side reads. Anyone with the project’s anon config can read whatever rules permit. Never rely on “the field is hidden in the UI” — if rules let it through, an attacker can read it.
Composite indexes are not created automatically in prod. Errors with a console link work in dev, but production needs the index checked in to firestore.indexes.json and deployed. Otherwise reads fail silently after release.
Firestore queries can’t mix range filters across fields. Only one field can have a range comparison per query (Firestore’s indexing model). Restructure into a composite key, denormalise, or fall back to client-side filtering on a smaller result.

Go deeperSee also

Firebase FAQ

What is Firebase Firestore?

Cloud Firestore is Firebase's scalable NoSQL document database. Data is organised in collections of documents; documents hold key-value fields and can contain nested sub-collections. Firestore supports real-time listeners that push updates to clients instantly, offline persistence on mobile/web, and strong consistency guarantees within a document.

How do Firebase security rules work?

Firestore security rules are server-side expressions that control read and write access to documents. Rules are written in a custom language, deployed with the Firebase CLI, and evaluated against the incoming request and document data. A rule must explicitly allow access; everything is denied by default. Test rules with the Firebase Emulator before deploying.

What is the difference between Firestore and the Firebase Realtime Database?

Firestore offers richer querying (compound filters, ordering, pagination), stronger consistency, better scalability, and a document-collection model. Realtime Database stores a single large JSON tree, has lower latency for simple key lookups, and has a simpler pricing model for high-frequency small writes. Most new projects should choose Firestore.

How do I use Firebase Authentication?

Enable the auth providers you need in the Firebase console (email/password, Google, GitHub, anonymous, etc.). On the client, call the appropriate sign-in method from the Firebase SDK. The SDK manages ID tokens automatically. On the server, verify tokens with the Admin SDK using auth.verify_id_token(token), which returns the decoded user claims.

How do I run Firebase locally with the emulator suite?

Install the Firebase CLI and run firebase emulators:start. This spins up local emulators for Firestore, Auth, Functions, Storage, and more, wired together without touching production. Point your app at the emulators by calling connectFirestoreEmulator() and related methods before any SDK calls. Use the Emulator UI at localhost:4000 to inspect data and logs.