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.

June 11, 2026by PromptGenius Team
firebasecursor-rulesbaasfirestorebackendmobileweb
Firebase Cursor Rules: Google Backend-as-a-Service

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 };
});