npx skills add firebase/agent-skills --skill firebase-firestore
ma1orek/replay · Archived
firebase-firestore
Build with Firestore NoSQL database - real-time sync, offline support, and scalable document storage. Use when: creating collections, querying documents, setting up security rules, handling real-time listeners, or troubleshooting permission-denied, quota exceeded, invalid query, or offline persistence errors. Prevents 10 documented errors.
Installation
npx skills add ma1orek/replay --skill firebase-firestore
Stronger alternatives
This repository is archived — consider an actively maintained alternative.
Apply typography, color theory, spacing systems, and iconography principles to create cohesive …
33 installsWhen the user wants to write, rewrite, or improve marketing copy for any page — including homep…
29 installsMaster effective code review practices to provide constructive feedback, catch bugs early, and …
29 installsMaster Unity ECS (Entity Component System) with DOTS, Jobs, and Burst for high-performance game…
26 installsSimilar popular skills
Related neighbors and high-traction skills in the same topics — useful to compare before installing.
>- Sets up, manages, queries, and configures Cloud Firestore databases (Standard/Enterprise edi…
107.6K installsComprehensive guide for Firestore basics including provisioning, security rules, and SDK usage.…
1.6K installsBuild with Firestore NoSQL database - real-time sync, offline support, and scalable document st…
439 installsAlso in this package
Other skills from ma1orek/replay · top by installs.
npx skills add ma1orek/replay
More details
Agent compatibility
Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.
Also listed on
Alternate registries and mirrors of this skill.
Repository health
main
Package contents
Files included with this skill beyond the listing page.
-
skill md
SKILL.md19,771 B -
docs
SUMMARY.md367 B
History
- First seen on skills.sh
- First recorded snapshot · 1 installs
SKILL.md
Firebase Firestore Database
Status: Production Ready Last Updated: 2026-01-25 Dependencies: None (standalone skill) Latest Versions: [email protected], [email protected]
Quick Start (5 Minutes)
1. Install Firebase SDK
# Client SDK (web/mobile)
npm install firebase
# Admin SDK (server/backend)
npm install firebase-admin
2. Initialize Firebase (Client)
// src/lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.FIREBASE_APP_ID,
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
3. Initialize Firebase Admin (Server)
// src/lib/firebase-admin.ts
import { initializeApp, cert, getApps } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';
// Initialize only once
if (!getApps().length) {
initializeApp({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
// Replace escaped newlines in private key
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
}),
});
}
export const adminDb = getFirestore();
CRITICAL:
- Never expose
FIREBASEPRIVATEKEYin client code - Use Admin SDK for server-side operations (bypasses security rules)
- Use Client SDK for authenticated user operations
Core Operations
Document CRUD (Client SDK - Modular v9+)
import {
collection,
doc,
addDoc,
getDoc,
getDocs,
setDoc,
updateDoc,
deleteDoc,
query,
where,
orderBy,
limit,
serverTimestamp,
Timestamp,
} from 'firebase/firestore';
import { db } from './firebase';
// CREATE - Auto-generated ID
const docRef = await addDoc(collection(db, 'users'), {
name: 'John Doe',
email: '[email protected]',
createdAt: serverTimestamp(),
});
console.log('Created document with ID:', docRef.id);
// CREATE - Specific ID
await setDoc(doc(db, 'users', 'user-123'), {
name: 'Jane Doe',
email: '[email protected]',
createdAt: serverTimestamp(),
});
// READ - Single document
const docSnap = await getDoc(doc(db, 'users', 'user-123'));
if (docSnap.exists()) {
console.log('Document data:', docSnap.data());
} else {
console.log('No such document!');
}
// READ - Collection with query
const q = query(
collection(db, 'users'),
where('email', '==', '[email protected]'),
orderBy('createdAt', 'desc'),
limit(10)
);
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.id, ' => ', doc.data());
});
// UPDATE - Merge fields (doesn't overwrite entire document)
await updateDoc(doc(db, 'users', 'user-123'), {
name: 'Jane Smith',
updatedAt: serverTimestamp(),
});
// UPDATE - Set with merge (creates if doesn't exist)
await setDoc(doc(db, 'users', 'user-123'), {
lastLogin: serverTimestamp(),
}, { merge: true });
// DELETE
await deleteDoc(doc(db, 'users', 'user-123'));
Document CRUD (Admin SDK)
import { adminDb } from './firebase-admin';
import { FieldValue, Timestamp } from 'firebase-admin/firestore';
// CREATE
const docRef = await adminDb.collection('users').add({
name: 'John Doe',
createdAt: FieldValue.serverTimestamp(),
});
// CREATE with specific ID
await adminDb.collection('users').doc('user-123').set({
name: 'Jane Doe',
createdAt: FieldValue.serverTimestamp(),
});
// READ
const doc = await adminDb.collection('users').doc('user-123').get();
if (doc.exists) {
console.log('Document data:', doc.data());
}
// READ with query
const snapshot = await adminDb
.collection('users')
.where('email', '==', '[email protected]')
.orderBy('createdAt', 'desc')
.limit(10)
.get();
snapshot.forEach((doc) => {
console.log(doc.id, '=>', doc.data());
});
// UPDATE
await adminDb.collection('users').doc('user-123').update({
name: 'Jane Smith',
updatedAt: FieldValue.serverTimestamp(),
});
// DELETE
await adminDb.collection('users').doc('user-123').delete();
Real-Time Listeners (Client SDK)
import { onSnapshot, query, where, collection, doc } from 'firebase/firestore';
import { db } from './firebase';
// Listen to single document
const unsubscribe = onSnapshot(doc(db, 'users', 'user-123'), (doc) => {
if (doc.exists()) {
console.log('Current data:', doc.data());
}
});
// Listen to collection with query
const q = query(
collection(db, 'messages'),
where('roomId', '==', 'room-123'),
orderBy('createdAt', 'desc'),
limit(50)
);
const unsubscribeMessages = onSnapshot(q, (querySnapshot) => {
const messages: Message[] = [];
querySnapshot.forEach((doc) => {
messages.push({ id: doc.id, ...doc.data() } as Message);
});
// Update UI with messages
setMessages(messages);
});
// Handle errors
const unsubscribeWithError = onSnapshot(
doc(db, 'users', 'user-123'),
(doc) => {
// Handle updates
},
(error) => {
console.error('Listener error:', error);
// Handle permission denied, etc.
}
);
// IMPORTANT: Unsubscribe when done (React useEffect cleanup)
useEffect(() => {
const unsubscribe = onSnapshot(/* ... */);
return () => unsubscribe();
}, []);
CRITICAL:
- Always unsubscribe from listeners to prevent memory leaks
- Listeners count against your concurrent connection limit
- Each listener is a WebSocket connection
Query Patterns
Compound Queries
import { query, where, orderBy, limit, startAfter, collection } from 'firebase/firestore';
// Multiple where clauses (requires composite index)
const q = query(
collection(db, 'products'),
where('category', '==', 'electronics'),
where('price', '<=', 1000),
orderBy('price', 'asc')
);
// Range query (only one field can have inequality)
const rangeQuery = query(
collection(db, 'events'),
where('date', '>=', new Date('2025-01-01')),
where('date', '<=', new Date('2025-12-31')),
orderBy('date', 'asc')
);
// Array contains
const arrayQuery = query(
collection(db, 'posts'),
where('tags', 'array-contains', 'firebase')
);
// Array contains any (max 30 values)
const arrayAnyQuery = query(
collection(db, 'posts'),
where('tags', 'array-contains-any', ['firebase', 'google', 'cloud'])
);
// In query (max 30 values)
const inQuery = query(
collection(db, 'users'),
where('status', 'in', ['active', 'pending'])
);
// Not in query (max 10 values)
const notInQuery = query(
collection(db, 'users'),
where('status', 'not-in', ['banned', 'deleted'])
);
Pagination with Cursors
import { query, orderBy, limit, startAfter, getDocs, collection, DocumentSnapshot } from 'firebase/firestore';
let lastVisible: DocumentSnapshot | null = null;
async function getNextPage() {
let q = query(
collection(db, 'posts'),
orderBy('createdAt', 'desc'),
limit(10)
);
if (lastVisible) {
q = query(q, startAfter(lastVisible));
}
const snapshot = await getDocs(q);
// Save last document for next page
lastVisible = snapshot.docs[snapshot.docs.length - 1] || null;
return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
}
Collection Group Queries
import { collectionGroup, query, where, getDocs } from 'firebase/firestore';
// Query across all subcollections named 'comments'
// Structure: posts/{postId}/comments/{commentId}
const q = query(
collectionGroup(db, 'comments'),
where('authorId', '==', 'user-123')
);
const snapshot = await getDocs(q);
// Returns all comments by user-123 across all posts
CRITICAL: Collection group queries require an index. Create in Firebase Console or deploy via firestore.indexes.json.
Batch Operations & Transactions
Batch Writes (Up to 500 operations)
import { writeBatch, doc, collection, serverTimestamp } from 'firebase/firestore';
import { db } from './firebase';
const batch = writeBatch(db);
// Add multiple documents
const usersRef = collection(db, 'users');
batch.set(doc(usersRef), { name: 'User 1', createdAt: serverTimestamp() });
batch.set(doc(usersRef), { name: 'User 2', createdAt: serverTimestamp() });
// Update existing document
batch.update(doc(db, 'counters', 'users'), { total: 100 });
// Delete document
batch.delete(doc(db, 'temp', 'old-doc'));
// Commit all operations atomically
await batch.commit();
Transactions (Read then Write)
import { runTransaction, doc, increment } from 'firebase/firestore';
import { db } from './firebase';
// Transfer credits between users
async function transferCredits(fromId: string, toId: string, amount: number) {
await runTransaction(db, async (transaction) => {
const fromRef = doc(db, 'users', fromId);
const toRef = doc(db, 'users', toId);
const fromDoc = await transaction.get(fromRef);
const toDoc = await transaction.get(toRef);
if (!fromDoc.exists() || !toDoc.exists()) {
throw new Error('User not found');
}
const fromCredits = fromDoc.data().credits;
if (fromCredits < amount) {
throw new Error('Insufficient credits');
}
transaction.update(fromRef, { credits: fromCredits - amount });
transaction.update(toRef, { credits: increment(amount) });
});
}
CRITICAL:
- Transactions can fail and retry automatically (up to 5 times)
- Don't perform side effects inside transaction (may run multiple times)
- All reads must come before writes in a transaction
Security Rules
Basic Rules Structure
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Helper functions
function isAuthenticated() {
return request.auth != null;
}
function isOwner(userId) {
return request.auth.uid == userId;
}
function isValidUser() {
return request.resource.data.keys().hasAll(['name', 'email'])
&& request.resource.data.name is string
&& request.resource.data.email is string;
}
// Users collection
match /users/{userId} {
allow read: if isAuthenticated();
allow create: if isAuthenticated() && isOwner(userId) && isValidUser();
allow update: if isOwner(userId);
allow delete: if isOwner(userId);
}
// Posts collection with subcollections
match /posts/{postId} {
allow read: if resource.data.published == true || isOwner(resource.data.authorId);
allow create: if isAuthenticated() && request.resource.data.authorId == request.auth.uid;
allow update, delete: if isOwner(resource.data.authorId);
// Comments subcollection
match /comments/{commentId} {
allow read: if true;
allow create: if isAuthenticated();
allow update, delete: if isOwner(resource.data.authorId);
}
}
// Admin-only collection
match /admin/{document=**} {
allow read, write: if request.auth.token.admin == true;
}
}
}
Deploy Rules
# Deploy rules
firebase deploy --only firestore:rules
# Deploy rules and indexes
firebase deploy --only firestore
Indexes
Composite Indexes (firestore.indexes.json)
{
"indexes": [
{
"collectionGroup": "products",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "category", "order": "ASCENDING" },
{ "fieldPath": "price", "order": "ASCENDING" }
]
},
{
"collectionGroup": "comments",
"queryScope": "COLLECTION_GROUP",
"fields": [
{ "fieldPath": "authorId", "order": "ASCENDING" },
{ "fieldPath": "createdAt", "order": "DESCENDING" }
]
}
],
"fieldOverrides": []
}
# Deploy indexes
firebase deploy --only firestore:indexes
CRITICAL:
- Firestore auto-creates single-field indexes
- Composite indexes must be created manually or via error link
- Collection group queries always require an index
Offline Persistence
Enable Offline Support (Web)
import { initializeFirestore, persistentLocalCache, persistentMultipleTabManager } from 'firebase/firestore';
import { app } from './firebase';
// Enable multi-tab offline persistence
const db = initializeFirestore(app, {
localCache: persistentLocalCache({
tabManager: persistentMultipleTabManager()
})
});
// OR: Enable single-tab persistence (simpler)
import { enableIndexedDbPersistence, getFirestore } from 'firebase/firestore';
const db = getFirestore(app);
enableIndexedDbPersistence(db).catch((err) => {
if (err.code === 'failed-precondition') {
// Multiple tabs open, persistence can only be enabled in one tab
console.warn('Persistence failed: multiple tabs open');
} else if (err.code === 'unimplemented') {
// Browser doesn't support persistence
console.warn('Persistence not supported');
}
});
Handle Offline State
import { onSnapshot, doc, SnapshotMetadata } from 'firebase/firestore';
onSnapshot(doc(db, 'users', 'user-123'), (doc) => {
const source = doc.metadata.fromCache ? 'local cache' : 'server';
console.log(`Data came from ${source}`);
if (doc.metadata.hasPendingWrites) {
console.log('Local changes pending sync');
}
});
Data Modeling Best Practices
Denormalization for Read Performance
// Instead of joining users and posts...
// Store author info directly in post document
// posts/{postId}
{
title: 'My Post',
content: '...',
authorId: 'user-123',
// Denormalized author data for fast reads
author: {
name: 'John Doe',
avatarUrl: 'https://...'
},
createdAt: Timestamp
}
Subcollections vs Root Collections
// Subcollections: Good for parent-child relationships
// posts/{postId}/comments/{commentId}
// - Easy to query all comments for a post
// - Deleting post doesn't auto-delete comments (use Cloud Functions)
// Root collections: Good for cross-cutting queries
// comments (with postId field)
// - Easy to query all comments by a user across posts
// - Requires manual data consistency
Counter Pattern (High-Write Scenarios)
// Direct increment (low traffic)
await updateDoc(doc(db, 'posts', postId), {
viewCount: increment(1)
});
// Distributed counter (high traffic - 1000+ writes/sec)
// Use Cloud Functions to aggregate shard counts
// counters/{counterId}/shards/{shardId}
Error Handling
Common Errors and Solutions
| Error | Cause | Solution |
|---|---|---|
permission-denied |
Security rules blocking access | Check rules, ensure user authenticated |
not-found |
Document doesn't exist | Use exists() check before accessing data |
already-exists |
Document with ID already exists | Use setDoc with merge or generate new ID |
resource-exhausted |
Quota exceeded | Upgrade plan or optimize queries |
failed-precondition |
Index missing for query | Create composite index (link in error) |
unavailable |
Service temporarily unavailable | Implement retry with backoff |
invalid-argument |
Invalid query combination | Check query constraints (see below) |
deadline-exceeded |
Operation timeout | Reduce data size or paginate |
Query Constraints
// INVALID: Multiple inequality filters on different fields
query(collection(db, 'posts'),
where('date', '>', startDate),
where('likes', '>', 100) // ERROR: Can't use inequality on second field
);
// VALID: Use range on one field, equality on others
query(collection(db, 'posts'),
where('category', '==', 'tech'),
where('date', '>', startDate)
);
// INVALID: orderBy field different from inequality field
query(collection(db, 'posts'),
where('date', '>', startDate),
orderBy('likes') // ERROR: Must orderBy('date') first
);
// VALID: orderBy inequality field first
query(collection(db, 'posts'),
where('date', '>', startDate),
orderBy('date'),
orderBy('likes')
);
Known Issues Prevention
This skill prevents 10 documented Firestore errors:
| Issue # | Error/Issue | Description | How to Avoid | Source |
|---|---|---|---|---|
| #1 | permission-denied |
Security rules blocking operation | Test rules in Firebase Console emulator first | Common |
| #2 | failed-precondition (index) |
Composite index missing | Click error link to create index, or define in firestore.indexes.json | Common |
| #3 | Invalid query combination | Multiple inequality filters | Use inequality on one field only, equality on others | Docs |
| #4 | Memory leak from listeners | Not unsubscribing from onSnapshot | Always call unsubscribe in cleanup (useEffect return) | Common |
| #5 | Offline persistence conflict | Multiple tabs with persistence | Use persistentMultipleTabManager() or handle error |
Docs |
| #6 | Transaction side effects | Side effects run multiple times | Never perform side effects inside runTransaction | Docs |
| #7 | Batch limit exceeded | More than 500 operations | Split into multiple batches | Docs |
| #8 | resource-exhausted |
Quota limits hit | Implement pagination, reduce reads, use caching | Common |
| #9 | Private key newline issue | \\n not converted in env var |
Use .replace(/\\n/g, '\n') on private key |
Common |
| #10 | Collection group query fails | Missing collection group index | Create index with queryScope: COLLECTION_GROUP |
Docs |
Firebase CLI Commands
# Initialize Firestore
firebase init firestore
# Start emulators
firebase emulators:start --only firestore
# Deploy rules and indexes
firebase deploy --only firestore
# Export data (for backup)
gcloud firestore export gs://your-bucket/backups/$(date +%Y%m%d)
# Import data
gcloud firestore import gs://your-bucket/backups/20250125
Package Versions (Verified 2026-01-25)
{
"dependencies": {
"firebase": "^12.8.0"
},
"devDependencies": {
"firebase-admin": "^13.6.0"
}
}
Official Documentation
- Firestore Overview: https://firebase.google.com/docs/firestore
- Get Started: https://firebase.google.com/docs/firestore/quickstart
- Data Model: https://firebase.google.com/docs/firestore/data-model
- Security Rules: https://firebase.google.com/docs/firestore/security/get-started
- Query Data: https://firebase.google.com/docs/firestore/query-data/queries
- Manage Data: https://firebase.google.com/docs/firestore/manage-data/add-data
- Offline Data: https://firebase.google.com/docs/firestore/manage-data/enable-offline
Last verified: 2026-01-25 | Skill version: 1.0.0