Skilllibrary firebase-sdk
git clone https://github.com/merceralex397-collab/skilllibrary
T=$(mktemp -d) && git clone --depth=1 https://github.com/merceralex397-collab/skilllibrary "$T" && mkdir -p ~/.claude/skills && cp -r "$T/09-backend-api-and-data/firebase-sdk" ~/.claude/skills/merceralex397-collab-skilllibrary-firebase-sdk && rm -rf "$T"
09-backend-api-and-data/firebase-sdk/SKILL.mdPurpose
Initialize and use the Firebase client SDK (v9 modular API) and Admin SDK (Node.js/Python) for Firestore CRUD, Firebase Auth, Cloud Storage, real-time listeners, transactions, batch writes, and Cloud Functions. This skill covers correct initialization, query composition, listener lifecycle management, and offline persistence configuration.
When to use this skill
Use this skill when:
- initializing Firebase client SDK (
) or Admin SDK (initializeApp
)admin.initializeApp - performing Firestore CRUD operations (addDoc, getDoc, setDoc, updateDoc, deleteDoc, getDocs)
- composing Firestore queries with where, orderBy, limit, startAfter
- setting up real-time listeners with onSnapshot
- implementing Firebase Auth flows (email/password, OAuth, onAuthStateChanged)
- working with Cloud Storage (uploadBytes, getDownloadURL)
- writing Cloud Functions (onCall, onRequest, Firestore triggers)
- using transactions or batch writes for atomic operations
- configuring offline persistence
Do not use this skill when
- the task is about writing or testing Firestore security rules — use
insteadfirebase-rules - the task is about BigQuery export or analytics queries — use
insteadbigquery - the task is purely about data model design without SDK code — use
insteaddata-model - the task is about WebSocket connections not using Firebase — use
insteadrealtime-websocket
Operating procedure
-
Determine the environment. Client (browser/React Native) uses the modular v9 SDK. Server (Node.js Cloud Functions, scripts) uses the Admin SDK. Never use Admin SDK in client code.
-
Initialize the SDK.
Client SDK (v9 modular):
import { initializeApp } from 'firebase/app'; import { getFirestore } from 'firebase/firestore'; import { getAuth } from 'firebase/auth'; const app = initializeApp({ apiKey: '...', authDomain: '...', projectId: '...', storageBucket: '...', messagingSenderId: '...', appId: '...', }); const db = getFirestore(app); const auth = getAuth(app);Admin SDK (Node.js):
const admin = require('firebase-admin'); // In Cloud Functions or GCP — uses Application Default Credentials admin.initializeApp(); // Or with explicit service account admin.initializeApp({ credential: admin.credential.cert(serviceAccount), }); const db = admin.firestore(); -
Perform Firestore operations.
import { collection, doc, addDoc, getDoc, getDocs, setDoc, updateDoc, deleteDoc, query, where, orderBy, limit } from 'firebase/firestore'; // Create const docRef = await addDoc(collection(db, 'posts'), { title: 'Hello', author: uid }); // Read single const snap = await getDoc(doc(db, 'posts', postId)); if (snap.exists()) { console.log(snap.data()); } // Read with query const q = query(collection(db, 'posts'), where('author', '==', uid), orderBy('createdAt', 'desc'), limit(20)); const querySnap = await getDocs(q); querySnap.forEach(doc => console.log(doc.id, doc.data())); // Update await updateDoc(doc(db, 'posts', postId), { title: 'Updated' }); // Delete await deleteDoc(doc(db, 'posts', postId)); -
Set up real-time listeners. Always store the unsubscribe function and call it on cleanup.
import { onSnapshot } from 'firebase/firestore'; const unsubscribe = onSnapshot( query(collection(db, 'messages'), where('roomId', '==', roomId), orderBy('timestamp')), (snapshot) => { snapshot.docChanges().forEach(change => { if (change.type === 'added') handleNewMessage(change.doc.data()); if (change.type === 'modified') handleUpdatedMessage(change.doc.data()); if (change.type === 'removed') handleRemovedMessage(change.doc.data()); }); }, (error) => { console.error('Listener error:', error); } ); // On component unmount or cleanup: unsubscribe(); -
Implement Auth flows.
import { signInWithEmailAndPassword, signInWithPopup, GoogleAuthProvider, onAuthStateChanged, signOut } from 'firebase/auth'; // Email/password const { user } = await signInWithEmailAndPassword(auth, email, password); // Google OAuth const provider = new GoogleAuthProvider(); const { user } = await signInWithPopup(auth, provider); // Listen for auth state const unsubscribe = onAuthStateChanged(auth, (user) => { if (user) { /* signed in */ } else { /* signed out */ } }); // Sign out await signOut(auth); -
Use transactions for atomic operations.
import { runTransaction } from 'firebase/firestore'; await runTransaction(db, async (transaction) => { const counterRef = doc(db, 'counters', 'visits'); const counterSnap = await transaction.get(counterRef); const newCount = (counterSnap.data()?.count || 0) + 1; transaction.update(counterRef, { count: newCount }); }); -
Use batch writes for multiple operations.
import { writeBatch } from 'firebase/firestore'; const batch = writeBatch(db); batch.set(doc(db, 'posts', id1), { title: 'Post 1' }); batch.update(doc(db, 'posts', id2), { views: 100 }); batch.delete(doc(db, 'posts', id3)); await batch.commit(); // Atomic — all succeed or all fail -
Configure offline persistence (web).
import { enableIndexedDbPersistence } from 'firebase/firestore'; // Call once after getFirestore, before any reads/writes enableIndexedDbPersistence(db).catch((err) => { if (err.code === 'failed-precondition') { /* Multiple tabs open */ } if (err.code === 'unimplemented') { /* Browser doesn't support */ } }); -
Deploy and test. Use the Firebase Emulator Suite for local development:
firebase emulators:start --only firestore,auth,functions,storagePoint SDK at emulators in development:
import { connectFirestoreEmulator } from 'firebase/firestore'; import { connectAuthEmulator } from 'firebase/auth'; connectFirestoreEmulator(db, 'localhost', 8080); connectAuthEmulator(auth, 'http://localhost:9099');
Decision rules
- Use v9 modular imports for new code — they enable tree-shaking and reduce bundle size. Only use compat (
) when migrating legacy codebases.firebase/compat - Use Admin SDK exclusively in trusted server environments (Cloud Functions, backend servers). Never bundle
in client code.firebase-admin - Prefer
when you want auto-generated IDs; useaddDoc
when you need a specific document ID.setDoc - Always attach an error callback to
— without it, listener errors are silently swallowed.onSnapshot - Unsubscribe all listeners on component unmount or route change to prevent memory leaks.
- Use transactions when reads and writes must be atomic. Use batch writes when you have multiple independent writes that should all succeed or all fail.
- Create composite indexes for queries that combine
withwhere
on different fields — Firestore will throw FAILED_PRECONDITION without them.orderBy - For pagination, use
with a consistentstartAfter(lastDoc)
, not offset-based pagination.orderBy
Output requirements
— initialization code for the correct environment (client vs. server)SDK Setup
— Firestore read/write code with proper error handlingData Operations
— onSnapshot setup with unsubscribe cleanupListener Management
— any composite indexes needed (for firestore.indexes.json)Index Requirements
References
Read these when working on specific aspects:
— SDK initialization, query patterns, listener managementreferences/implementation-patterns.md
— security and correctness checksreferences/validation-checklist.md
— common errors and their fixesreferences/failure-modes.md
Related skills
— security rules that govern what SDK operations are allowedfirebase-rules
— Firestore document/collection schema designdata-model
— alternative real-time approaches beyond Firestore listenersrealtime-websocket
— API design patterns for Cloud Functions endpointsapi-contracts
Anti-patterns
- Using Admin SDK in client-side code. Admin SDK bypasses all security rules and has full read/write access. Bundling it in a browser app exposes your entire database.
- Not unsubscribing onSnapshot listeners. Each active listener holds a connection and receives updates. Forgetting to unsubscribe causes memory leaks and unexpected behavior as listeners accumulate.
- Using
withoutsetDoc
when you want a partial update. Plain{ merge: true }
overwrites the entire document. UsesetDoc
for partial updates, orupdateDoc
withsetDoc
.{ merge: true } - Ignoring the Firestore index requirement. Composite queries (where + orderBy on different fields) require indexes. Not creating them causes runtime FAILED_PRECONDITION errors. Check the error message — it contains a direct link to create the index.
- Reading an entire collection with
. This fetches every document and scales poorly. Always use queries withgetDocs(collection(db, 'users'))
,where
, and pagination.limit - Not handling offline state. With persistence enabled, writes queue locally when offline. The app must handle
metadata and inform users about pending writes.fromCache - Storing sensitive data in client-accessible collections. Even with rules, prefer to keep truly sensitive data in server-only collections accessed via Cloud Functions.
Failure handling
- FAILED_PRECONDITION on query. Missing composite index. Check the error message for a link to create it in the Firebase Console, or add it to
and deploy withfirestore.indexes.json
.firebase deploy --only firestore:indexes - Auth token expired. Firebase Auth tokens expire after 1 hour. The SDK automatically refreshes them, but if the refresh fails (network error), operations will fail. Check
for null user state.onAuthStateChanged - Offline cache conflicts. When persistence is enabled and the device comes back online, local writes are synced. If server-side rules deny the write, the local cache may show data that gets reverted. Handle the
flag.metadata.hasPendingWrites - onSnapshot listener accumulation. Each call to
opens a new listener. If called in a React component without cleanup, listeners multiply on re-renders. Always useonSnapshot
cleanup or equivalent.useEffect - Transaction contention. Transactions retry up to 5 times if the underlying data changes during execution. Under high contention, they may exhaust retries and fail. Use batch writes when operations don't need to read first.
- CORS on Storage downloads.
returns a URL that may be subject to CORS. Configure CORS on the Storage bucket usinggetDownloadURL
.gsutil cors set cors.json gs://your-bucket - Missing service account permissions. Admin SDK in Cloud Functions uses the default service account. If custom permissions are needed (e.g., for other GCP services), configure the service account in the Cloud Console.