Firebase Cursor Rules: Google Backend-as-a-Service
Cursor rules for Firebase covering Firestore, Authentication, Cloud Functions, Realtime Database, Storage, Hosting, Security Rules, and the emulator suite.

Overview
Firebase is Google's backend-as-a-service platform providing Firestore (NoSQL database), Authentication, Cloud Functions, Realtime Database, Storage, and Hosting — all designed to work together. These cursor rules enforce Firestore query patterns, security rules, auth flows, Cloud Functions organization, and emulator-driven local development so AI assistants generate secure, scalable Firebase code.
Note:
Enforces Firestore query patterns (no client-side joins), security rules for all data access, Auth integration with Firestore, Cloud Functions triggers (onCall, onRequest, onDocumentWritten), emulator suite usage, and Admin SDK patterns.
Rules Configuration
---
description: Enforces Firebase best practices including Firestore query patterns, Security Rules, Auth integration, Cloud Functions triggers, emulator suite usage, and Admin SDK vs client SDK separation.
globs: **/*.js,**/*.ts,**/*.jsx,**/*.tsx,firebase.json,firestore.rules,storage.rules
---
# Firebase Best Practices
You are an expert in Firebase, serverless backend development, and Google Cloud Platform.
You understand Firestore data modeling, security rules, authentication flows, and production deployment.
### Client SDK Setup
- Import modular SDK: `import { initializeApp } from "firebase/app"`
- Import only what you need: `getFirestore`, `getAuth`, `getStorage` (tree-shaking)
- Never import the full firebase package: `import firebase from "firebase/app"` (legacy)
- Use environment variables for Firebase config (they're safe to expose in client)
- Use `getFirestore(app)` to get a Firestore instance — reuse across the app
### Firestore
- Collections contain documents, documents contain subcollections or fields
- Use `.collection("users").doc(userId)` for document references
- Query with `.where("status", "==", "active").orderBy("createdAt", "desc").limit(10)`
- Use compound queries with composite indexes (create them in console or firestore.indexes.json)
- Use `.onSnapshot()` for realtime listeners — always return the unsubscribe function
- Use `serverTimestamp()` for createdAt/updatedAt: `import { serverTimestamp } from "firebase/firestore"`
- Use `increment()`, `arrayUnion()`, `arrayRemove()` for atomic field updates
- Use `runTransaction()` for atomic read-then-write operations
- Paginate with `startAfter(lastDoc)` and `.limit()` — no offset-based pagination
- Never use `where("field", "!=", "value")` without an equality filter first
### Security Rules
- Write rules that deny all by default, then allow specific access
- Check `request.auth != null` for authenticated routes
- Use `request.auth.uid == userId` for user-scoped data
- Validate data shape: `request.resource.data.name is string`
- Validate field presence: `request.resource.data.keys().hasOnly(['name', 'email'])`
- Limit query size: check request.query for malicious queries
- Function-based rules for complex logic: `function isOwner() { return request.auth.uid == resource.data.userId }`
- Test rules with the emulator: `firebase emulators:start`
### Authentication
- Use `signInWithEmailAndPassword(auth, email, password)` for login
- Use `createUserWithEmailAndPassword(auth, email, password)` then store profile in Firestore
- Use `signInWithPopup(auth, provider)` for Google/GitHub OAuth
- Use `onAuthStateChanged(auth, callback)` to react to auth state changes
- Store user profiles in Firestore `users/{uid}` — not in auth metadata
- Use FirebaseUI for drop-in auth UI (web)
- Never store plain text passwords — Firebase handles hashing
### Cloud Functions
- Use `onCall()` for callable functions from the client SDK
- Use `onRequest()` for HTTP-triggered functions (webhooks, REST APIs)
- Use `onDocumentCreated()`, `onDocumentUpdated()`, `onDocumentDeleted()` for Firestore triggers
- Use `onObjectFinalized()` for Storage triggers
- Export functions from `index.ts` individually, not as a single default export
- Use `region("us-central1")` to deploy closer to Firestore location
- Set `maxInstances` and `timeoutSeconds` to control scaling
- Never include secrets in function code — use `firebase functions:config:set`
- Use admin SDK in functions: `import { getFirestore } from "firebase-admin/firestore"`
### Realtime Database
- Structure data as flat as possible — avoid deep nesting
- Use `.ref("users/" + uid).set({ name, email })` to write
- Use `.ref("users/" + uid).on("value", callback)` for realtime reads
- Use `.ref("users").orderByChild("name").equalTo("Alice")` for queries
- Security rules: `.read` and `.write` with `auth` and `data` variables
### Storage
- Upload: `uploadBytes(storageRef, file)` or `uploadBytesResumable` for progress
- Get download URL: `getDownloadURL(ref(storage, "path/to/file"))`
- Security rules: `request.auth != null && request.resource.size < 5 * 1024 * 1024`
- Delete: `deleteObject(ref(storage, "path/to/file"))`
- Use storage folders to organize: `users/{userId}/avatars/profile.jpg`
### Emulator Suite
- Run locally: `firebase emulators:start`
- Use emulators for all development — never test against production
- Connect Firebase SDK to emulators: `connectFirestoreEmulator(db, "localhost", 8080)`
- Test security rules against the Firestore emulator
- Use Auth emulator for testing user flows without real accounts
- Export/import emulator data: `firebase emulators:export ./data`
Installation
Create firebase.mdc in your project's .cursor/rules/ directory and paste the configuration above. Cursor and Windsurf both read .cursor/rules/ — Copilot users place it in .github/copilot-instructions.md instead.
# Initialize Firebase in your project
npm install firebase
npm install -g firebase-tools
firebase login
firebase init
Examples
// firebase.ts — Client SDK setup
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth } from "firebase/auth";
import { getStorage } from "firebase/storage";
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const auth = getAuth(app);
export const storage = getStorage(app);
// user.service.ts — Firestore with auth integration
import { doc, setDoc, getDoc, serverTimestamp } from "firebase/firestore";
import { createUserWithEmailAndPassword } from "firebase/auth";
import { db, auth } from "./firebase";
export async function registerUser(email: string, password: string, name: string) {
const { user } = await createUserWithEmailAndPassword(auth, email, password);
await setDoc(doc(db, "users", user.uid), {
name,
email,
createdAt: serverTimestamp(),
});
return user;
}
export async function getUserProfile(uid: string) {
const snapshot = await getDoc(doc(db, "users", uid));
return snapshot.exists() ? snapshot.data() : null;
}
// Firestore security rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth != null;
allow create: if request.auth != null && request.auth.uid == userId
&& request.resource.data.keys().hasOnly(['name', 'email', 'createdAt']);
allow update: if request.auth != null && request.auth.uid == userId;
allow delete: if false; // never allow client-side deletion
}
match /posts/{postId} {
allow read: if true;
allow create: if request.auth != null
&& request.resource.data.authorId == request.auth.uid;
allow update, delete: if request.auth != null
&& resource.data.authorId == request.auth.uid;
}
}
}
// functions/src/index.ts — Cloud Functions with Firestore triggers
import * as functions from "firebase-functions/v2";
import { getFirestore } from "firebase-admin/firestore";
import { initializeApp } from "firebase-admin/app";
initializeApp();
const db = getFirestore();
export const onUserCreated = functions.firestore.onDocumentCreated(
"users/{userId}",
async (event) => {
const userData = event.data?.data();
if (!userData) return;
await db.collection("userStats").doc(event.params.userId).set({
postCount: 0,
joinedAt: userData.createdAt,
});
},
);
export const sendWelcomeEmail = functions.https.onCall(async (request) => {
const uid = request.auth?.uid;
if (!uid) throw new functions.https.HttpsError("unauthenticated", "Must be logged in");
const user = await db.collection("users").doc(uid).get();
const userData = user.data();
if (!userData) throw new functions.https.HttpsError("not-found", "User not found");
// Send email via SendGrid, Resend, etc.
return { success: true };
});
Related Resources
Related Articles
Java Cursor Rules: AI-Powered Development Best Practices
Cursor rules for Java development enforcing clean code standards, Spring Boot patterns, and best practices with AI assistance for production-ready applications.
Angular Cursor Rules: AI-Assisted Development Best Practices
Angular cursor rules for TypeScript and RxJS. Enforce clean architecture, component design, testing, and CLI workflows. Generate secure, maintainable code with complete project context.
Solidity Cursor Rules: Smart Contract Development Guide
Cursor rules for Solidity development covering smart contract patterns, gas optimization, security best practices, testing, and Ethereum blockchain integration.