
Using Firebase
- 25 installs
- 2 repo stars
- Updated December 29, 2025
- spillwavesolutions/using-firebase
Helps with ai & agent building tasks during AI-assisted development.
About
using-firebase is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- using-firebase
- AI & Agent Building
- AI-coding skill
Using Firebase by the numbers
- 25 all-time installs (skills.sh)
- Ranked #9,800 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/spillwavesolutions/using-firebase --skill using-firebaseAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 25 |
|---|---|
| repo stars | ★ 2 |
| Last updated | December 29, 2025 |
| Repository | spillwavesolutions/using-firebase ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Firebase Development Skill
Table of Contents
- Quick Start
- Scope
- Task Navigation
- Scripts
- Cloud Functions Generation
- Assets
- Common Workflows
- Pre-Deployment Checklist
- Emulator Ports
- Key Decisions
Quick Start
1. New project: Run scripts/init_project.sh [project-id] 2. Local development: Run scripts/start_emulators.sh 3. Deploy: Run scripts/deploy.sh
Scope
Use this skill for: Firebase development including Firestore CRUD/queries, Cloud Functions (1st/2nd gen), Firebase CLI, emulator setup, security rules, authentication, hosting, and GCP integration.
Do not use for: Pure GCP without Firebase, AWS/Azure services, non-serverless architectures, self-hosted solutions, or complex relational queries (use Cloud SQL instead).
Task Navigation
| Task | Action |
|---|---|
| Initialize Firebase project | scripts/init_project.sh |
| Start local emulators | scripts/start_emulators.sh |
| Deploy to production | scripts/deploy.sh |
| Deploy functions only | scripts/deploy_functions.sh |
| Set up Python functions | python scripts/setup_python_functions.py |
| Manage secrets | scripts/manage_secrets.sh |
| Export Firestore data | scripts/export_firestore.sh |
| Import Firestore data | scripts/import_firestore.sh |
| Topic | Reference |
|---|---|
| CLI commands | references/cli-commands.md |
| Firestore CRUD, queries, modeling | references/firestore.md |
| Cloud Functions triggers | references/functions-triggers.md |
| Error handling, optimization | references/functions-patterns.md |
| Security rules | references/security-rules.md |
| Authentication | references/auth-integration.md |
| Hosting configuration | references/hosting-config.md |
| GCP integration | references/gcp-integration.md |
Scripts
For complete CLI reference, see references/cli-commands.md.
init_project.sh
Initialize Firebase project with Firestore, Functions, Hosting, Storage, Emulators.
./scripts/init_project.sh # Interactive
./scripts/init_project.sh my-project # Specific projectstart_emulators.sh
Start emulator suite with data persistence.
./scripts/start_emulators.sh # Auto-persistence
./scripts/start_emulators.sh --debug # Enable debugging
./scripts/start_emulators.sh --import ./backup # Import data
./scripts/start_emulators.sh --only functions,firestoredeploy.sh
Deploy with safety confirmations.
./scripts/deploy.sh # Full deploy
./scripts/deploy.sh --dry-run # Preview only
./scripts/deploy.sh --only hosting # Specific target
./scripts/deploy.sh --force # Skip confirmationdeploy_functions.sh
Deploy Cloud Functions with granular control.
./scripts/deploy_functions.sh # All functions
./scripts/deploy_functions.sh myFunction # Single function
./scripts/deploy_functions.sh --codebase python # Specific codebasemanage_secrets.sh
Manage Cloud Functions secrets for 2nd gen functions. Uses GCP Secret Manager for secure storage. Prefer this script over direct gcloud commands for Firebase-integrated secret management with proper function access binding.
Secret lifecycle: Create secrets before first deploy, update via set (creates new version), bind to functions via runWith({ secrets: [...] }), and rotate by setting new values.
./scripts/manage_secrets.sh set API_KEY # Set secret (creates or updates)
./scripts/manage_secrets.sh get API_KEY # View metadata and versions
./scripts/manage_secrets.sh list # List all project secrets
./scripts/manage_secrets.sh delete API_KEY # Delete secret and all versionsexport_firestore.sh / import_firestore.sh
Backup and restore Firestore data.
./scripts/export_firestore.sh --emulator # From emulator
./scripts/export_firestore.sh --output gs://bucket # Production to GCS
./scripts/import_firestore.sh --input ./data --emulatorsetup_python_functions.py
Create Python Cloud Functions project.
python scripts/setup_python_functions.py --path python-functions --codebase pythonCloud Functions Generation
Use 2nd generation (recommended):
- HTTP, Firestore, Storage, Scheduled, Pub/Sub triggers
- Higher concurrency, longer timeouts
Use 1st generation only for:
- Auth
onCreate/onDeletetriggers (not available in 2nd gen)
2nd Gen Example (TypeScript)
import { onDocumentCreated } from "firebase-functions/v2/firestore";
import { onRequest } from "firebase-functions/v2/https";
export const onUserCreated = onDocumentCreated("users/{userId}", (event) => {
console.log("New user:", event.params.userId, event.data?.data());
});
export const api = onRequest({ cors: true }, (req, res) => {
res.json({ status: "ok" });
});1st Gen Auth Trigger
import * as functions from "firebase-functions/v1";
export const onUserCreate = functions.auth.user().onCreate((user) => {
console.log("New user:", user.uid);
return null;
});See references/functions-triggers.md for all trigger types with TypeScript and Python examples.
Assets
| File | Use |
|---|---|
assets/firebase.json.template | Copy to firebase.json and customize |
assets/firestore.rules.template | Copy to firestore.rules |
assets/storage.rules.template | Copy to storage.rules |
assets/tsconfig.functions.json | Copy to functions/tsconfig.json |
Common Workflows
New Project Setup
1. Run scripts/init_project.sh 2. Copy templates from assets/ directory 3. Start emulators: scripts/start_emulators.sh
Add Python Functions
1. Run python scripts/setup_python_functions.py 2. Update firebase.json with provided config 3. Deploy: scripts/deploy_functions.sh --codebase python
Security Rules Development
1. Start with assets/firestore.rules.template 2. Test with emulator 3. Deploy: firebase deploy --only firestore:rules
See references/security-rules.md for patterns.
Production Deployment
1. Set secrets: scripts/manage_secrets.sh set API_KEY 2. Dry run: scripts/deploy.sh --dry-run 3. Deploy: scripts/deploy.sh
Pre-Deployment Checklist
Before deploying to production, verify:
- [ ] Security Rules: Tested rules in emulator, no open access patterns
- [ ] Secrets: All required secrets configured via
scripts/manage_secrets.sh list - [ ] Environment: Correct project selected (
firebase use) - [ ] Functions: All functions tested locally with emulator
- [ ] Indexes: Firestore indexes deployed (
firebase deploy --only firestore:indexes) - [ ] Dry Run:
scripts/deploy.sh --dry-runshows expected changes - [ ] App Check: Enabled for production apps (prevents abuse)
- [ ] Billing: Budget alerts configured in GCP Console
- [ ] Monitoring: Cloud Logging and Error Reporting enabled
Emulator Ports
| Service | Port |
|---|---|
| Auth | 9099 |
| Functions | 5001 |
| Firestore | 8080 |
| Storage | 9199 |
| Hosting | 5000 |
| UI | 4000 |
Key Decisions
Firestore Data Modeling
- Embed data read together that rarely changes
- Reference data that changes frequently or is shared
- Subcollections for parent-child relationships
- Root collections for cross-document queries
See references/firestore.md for patterns.
TypeScript vs Python Functions
- TypeScript: JavaScript teams, Firebase client SDK integration
- Python: ML/data science, Python ecosystem
Both can coexist via multiple codebases in firebase.json.
# Firebase
.firebase/
firebase-debug.log
firebase-debug.*.log
.firebaserc
ui-debug.log
firestore-debug.log
pubsub-debug.log
database-debug.log
# Node.js (Firebase Functions)
node_modules/
functions/node_modules/
functions/lib/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Python (Cloud Functions)
__pycache__/
*.py[cod]
*$py.class
.Python
venv/
.venv/
env/
.env
*.egg-info/
dist/
build/
# IDE and editors
.idea/
.vscode/
*.swp
*.swo
*~
.DS_Store
# Secrets and credentials
*.pem
*.key
*.p12
*.json.enc
service-account*.json
*-credentials.json
secrets/
# Local emulator data
emulator-data/
.emulator/
# Coverage and testing
.coverage
htmlcov/
.pytest_cache/
.nyc_output/
coverage/
# Temporary files
*.tmp
*.temp
.cache/
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"functions": [
{
"source": "functions",
"codebase": "default",
"runtime": "nodejs20",
"ignore": [
"node_modules",
".git",
"firebase-debug.log",
"firebase-debug.*.log",
"*.local"
],
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build"
]
}
],
"hosting": {
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"cleanUrls": true,
"trailingSlash": false,
"rewrites": [
{
"source": "/api/**",
"function": {
"functionId": "api",
"region": "us-central1"
}
},
{
"source": "**",
"destination": "/index.html"
}
],
"headers": [
{
"source": "**/*.@(jpg|jpeg|gif|png|svg|webp|js|css|woff2)",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=31536000, immutable"
}
]
},
{
"source": "**",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
}
]
}
]
},
"storage": {
"rules": "storage.rules"
},
"emulators": {
"auth": {
"port": 9099
},
"functions": {
"port": 5001
},
"firestore": {
"port": 8080
},
"hosting": {
"port": 5000
},
"storage": {
"port": 9199
},
"ui": {
"enabled": true,
"port": 4000
},
"singleProjectMode": true
}
}
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// =========================================================================
// Helper Functions
// =========================================================================
// Authentication helpers
function isSignedIn() {
return request.auth != null;
}
function isOwner(userId) {
return isSignedIn() && request.auth.uid == userId;
}
function isAdmin() {
return isSignedIn() && request.auth.token.admin == true;
}
function hasRole(role) {
return isSignedIn() && request.auth.token.role == role;
}
// Document helpers
function isAuthor() {
return isSignedIn() && resource.data.authorId == request.auth.uid;
}
function willBeAuthor() {
return isSignedIn() && request.resource.data.authorId == request.auth.uid;
}
// Validation helpers
function hasRequiredFields(fields) {
return request.resource.data.keys().hasAll(fields);
}
function onlyAllowedFields(fields) {
return request.resource.data.keys().hasOnly(fields);
}
function isValidString(field, minLen, maxLen) {
return request.resource.data[field] is string
&& request.resource.data[field].size() >= minLen
&& request.resource.data[field].size() <= maxLen;
}
// =========================================================================
// User Documents
// =========================================================================
match /users/{userId} {
// Anyone signed in can read user profiles
allow read: if isSignedIn();
// Users can only write their own profile
allow create: if isOwner(userId)
&& hasRequiredFields(['email', 'createdAt'])
&& request.resource.data.createdAt == request.time;
allow update: if isOwner(userId);
allow delete: if isOwner(userId) || isAdmin();
// User's private subcollections
match /private/{docId} {
allow read, write: if isOwner(userId);
}
}
// =========================================================================
// Posts / Content
// =========================================================================
match /posts/{postId} {
// Public read
allow read: if true;
// Authenticated users can create with validation
allow create: if isSignedIn()
&& willBeAuthor()
&& hasRequiredFields(['title', 'content', 'authorId', 'createdAt'])
&& isValidString('title', 1, 200)
&& isValidString('content', 1, 50000)
&& request.resource.data.createdAt == request.time;
// Only author can update (or admin)
allow update: if (isAuthor() || isAdmin())
&& request.resource.data.authorId == resource.data.authorId // Can't change author
&& request.resource.data.createdAt == resource.data.createdAt; // Can't change created
// Only author or admin can delete
allow delete: if isAuthor() || isAdmin();
// Comments subcollection
match /comments/{commentId} {
allow read: if true;
allow create: if isSignedIn()
&& request.resource.data.authorId == request.auth.uid;
allow update, delete: if isSignedIn()
&& resource.data.authorId == request.auth.uid;
}
}
// =========================================================================
// Admin-only Collection
// =========================================================================
match /admin/{document=**} {
allow read, write: if isAdmin();
}
// =========================================================================
// App Configuration (read-only for users)
// =========================================================================
match /config/{docId} {
allow read: if true;
allow write: if isAdmin();
}
// =========================================================================
// Default Deny
// =========================================================================
// Deny access to any path not explicitly matched above
match /{document=**} {
allow read, write: if false;
}
}
}
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// =========================================================================
// Helper Functions
// =========================================================================
function isSignedIn() {
return request.auth != null;
}
function isOwner(userId) {
return isSignedIn() && request.auth.uid == userId;
}
function isAdmin() {
return isSignedIn() && request.auth.token.admin == true;
}
function isValidImage() {
return request.resource.contentType.matches('image/.*');
}
function isValidDocument() {
return request.resource.contentType in [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/plain'
];
}
function isUnderSizeLimit(maxSizeMB) {
return request.resource.size < maxSizeMB * 1024 * 1024;
}
// =========================================================================
// User-specific Storage
// =========================================================================
// Users can read/write to their own folder
match /users/{userId}/{allPaths=**} {
allow read: if isSignedIn();
allow write: if isOwner(userId) && isUnderSizeLimit(10);
}
// =========================================================================
// Profile Pictures
// =========================================================================
match /profiles/{userId}/avatar.{ext} {
// Anyone can view profile pictures
allow read: if true;
// User can upload their own avatar with validation
allow write: if isOwner(userId)
&& isValidImage()
&& isUnderSizeLimit(5)
&& ext.matches('jpg|jpeg|png|gif|webp');
}
// =========================================================================
// Public Assets
// =========================================================================
match /public/{allPaths=**} {
allow read: if true;
allow write: if isSignedIn() && isUnderSizeLimit(10);
}
// =========================================================================
// Private Documents
// =========================================================================
match /documents/{userId}/{docId} {
// Only owner can access
allow read, write: if isOwner(userId)
&& isValidDocument()
&& isUnderSizeLimit(25);
}
// =========================================================================
// Uploads pending processing
// =========================================================================
match /uploads/{uploadId} {
// Authenticated users can upload
allow create: if isSignedIn()
&& isUnderSizeLimit(50)
&& request.resource.metadata.uploadedBy == request.auth.uid;
// Only uploader can read their pending uploads
allow read: if isSignedIn()
&& resource.metadata.uploadedBy == request.auth.uid;
// Only backend can delete (via Cloud Function)
allow delete: if false;
}
// =========================================================================
// Admin-only Storage
// =========================================================================
match /admin/{allPaths=**} {
allow read, write: if isAdmin();
}
// =========================================================================
// Default Deny
// =========================================================================
match /{allPaths=**} {
allow read, write: if false;
}
}
}
{
"compilerOptions": {
"module": "commonjs",
"noImplicitReturns": true,
"noUnusedLocals": true,
"outDir": "lib",
"sourceMap": true,
"strict": true,
"target": "es2017",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true
},
"compileOnSave": true,
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"lib"
]
}
Firebase Development Skill
  
A comprehensive Claude Code skill for Firebase development on GCP-hosted applications.
Overview
This skill provides complete guidance for developing Firebase applications:
| Feature | Description |
|---|---|
| Firestore | CRUD operations, queries, transactions, data modeling |
| Cloud Functions | 1st and 2nd generation, TypeScript and Python |
| Security Rules | Firestore and Cloud Storage rule patterns |
| Authentication | Integration patterns and session management |
| Hosting | Configuration, rewrites, headers, and caching |
| GCP Integration | BigQuery, Cloud Tasks, Pub/Sub, and more |
Quick Start
1. Initialize project: scripts/init_project.sh [project-id] 2. Start emulators: scripts/start_emulators.sh 3. Deploy: scripts/deploy.sh
See SKILL.md for full documentation.
Installation
Skilz Universal Installer (Recommended)
The recommended way to install this skill across different AI coding agents is using the skilz universal installer.
Install Skilz
pip install skilzClaude Code
Install to user home (available in all projects):
skilz install -g https://github.com/SpillwaveSolutions/using-firebaseInstall to current project only:
skilz install -g https://github.com/SpillwaveSolutions/using-firebase --projectOpenCode
Install for OpenCode:
skilz install -g https://github.com/SpillwaveSolutions/using-firebase --agent opencodeProject-level install:
skilz install -g https://github.com/SpillwaveSolutions/using-firebase --project --agent opencodeGemini CLI
Project-level install for Gemini:
skilz install -g https://github.com/SpillwaveSolutions/using-firebase --agent geminiOpenAI Codex
Install for OpenAI Codex:
skilz install -g https://github.com/SpillwaveSolutions/using-firebase --agent codexProject-level install:
skilz install -g https://github.com/SpillwaveSolutions/using-firebase --project --agent codexGit URL Options
You can use either HTTPS or SSH URLs:
# HTTPS URL
skilz install -g https://github.com/SpillwaveSolutions/using-firebase
# SSH URL
skilz install --git git@github.com:SpillwaveSolutions/using-firebase.gitOther Supported Agents
Skilz supports 14+ coding agents including Windsurf, Qwen Code, Cursor, and more.
For the full list of supported platforms, visit SkillzWave.ai/platforms or see the skilz-cli GitHub repository.
Manual Installation
Copy this skill to your Claude Code skills directory:
cp -r using-firebase ~/.claude/skills/Or clone from the repository and install via the Claude Code skill manager.
Reference Documentation
| Reference | Description |
|---|---|
| firestore.md | CRUD, queries, transactions, data modeling |
| functions-triggers.md | All Cloud Functions trigger types |
| functions-patterns.md | Error handling, secrets, App Check |
| security-rules.md | Firestore and Storage rules |
| auth-integration.md | Authentication setup |
| hosting-config.md | Hosting configuration |
| gcp-integration.md | GCP service integration |
| cli-commands.md | Firebase CLI reference |
Scripts
| Script | Description |
|---|---|
init_project.sh | Initialize Firebase project with Firestore, Functions, Hosting |
start_emulators.sh | Start emulator suite with data persistence |
deploy.sh | Deploy with safety confirmations |
deploy_functions.sh | Deploy Cloud Functions with granular control |
manage_secrets.sh | Manage Cloud Functions secrets |
export_firestore.sh | Export Firestore data |
import_firestore.sh | Import Firestore data |
setup_python_functions.py | Create Python Cloud Functions project |
Asset Templates
| File | Use |
|---|---|
assets/firebase.json.template | Copy to firebase.json and customize |
assets/firestore.rules.template | Copy to firestore.rules |
assets/storage.rules.template | Copy to storage.rules |
assets/tsconfig.functions.json | Copy to functions/tsconfig.json |
Standards
This skill follows the AgentSkills.io standard for agentic skills, ensuring compatibility across multiple AI coding assistants.
Marketplace
Find this skill and more at SkillzWave.ai - the largest marketplace for agentic AI skills.
Direct link: using-firebase on SkillzWave
About
This skill is developed and maintained by the community. For more developer tools and resources, visit SpillWave.com - Leaders in AI Agent Development.
License
MIT License - feel free to use, modify, and distribute.
Contributing
Contributions are welcome! Please submit issues and pull requests to improve this skill.
Firebase Authentication Reference
Authentication setup, providers, and server-side integration.
Contents
- Client Setup
- Authentication Providers
- Server-side Verification
- Custom Claims
- Custom Tokens
- User Management
---
Client Setup
Web (Modular SDK)
import { initializeApp } from "firebase/app";
import {
getAuth,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signOut,
onAuthStateChanged
} from "firebase/auth";
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
// Listen for auth state changes
onAuthStateChanged(auth, (user) => {
if (user) {
console.log("Signed in:", user.uid, user.email);
} else {
console.log("Signed out");
}
});Connect to Emulator
import { connectAuthEmulator } from "firebase/auth";
if (location.hostname === "localhost") {
connectAuthEmulator(auth, "http://127.0.0.1:9099");
}---
Authentication Providers
Email/Password
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
sendPasswordResetEmail,
sendEmailVerification,
updatePassword
} from "firebase/auth";
// Sign up
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// Sign in
await signInWithEmailAndPassword(auth, email, password);
// Password reset
await sendPasswordResetEmail(auth, email);
// Email verification
await sendEmailVerification(auth.currentUser);
// Update password (requires recent sign-in)
await updatePassword(auth.currentUser, newPassword);Google Sign-in
import {
GoogleAuthProvider,
signInWithPopup,
signInWithRedirect,
getRedirectResult
} from "firebase/auth";
const provider = new GoogleAuthProvider();
provider.addScope("https://www.googleapis.com/auth/contacts.readonly");
// Popup (desktop)
const result = await signInWithPopup(auth, provider);
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential?.accessToken;
const user = result.user;
// Redirect (mobile-friendly)
await signInWithRedirect(auth, provider);
// On page load:
const result = await getRedirectResult(auth);Phone Authentication
import {
RecaptchaVerifier,
signInWithPhoneNumber
} from "firebase/auth";
// Setup reCAPTCHA
const recaptchaVerifier = new RecaptchaVerifier(auth, "recaptcha-container", {
size: "invisible",
callback: (response) => console.log("reCAPTCHA solved")
});
// Send verification code
const confirmationResult = await signInWithPhoneNumber(
auth,
"+1234567890",
recaptchaVerifier
);
// Verify code
const credential = await confirmationResult.confirm(verificationCode);
const user = credential.user;Anonymous Sign-in
import { signInAnonymously } from "firebase/auth";
const userCredential = await signInAnonymously(auth);
// User is now signed in anonymously
// user.isAnonymous === trueLink Multiple Providers
import { linkWithPopup, GoogleAuthProvider } from "firebase/auth";
// Link anonymous account to Google
const provider = new GoogleAuthProvider();
const result = await linkWithPopup(auth.currentUser, provider);Sign Out
import { signOut } from "firebase/auth";
await signOut(auth);---
Server-side Verification
Verify ID Token
TypeScript (Admin SDK):
import { getAuth } from "firebase-admin/auth";
async function verifyToken(idToken: string) {
try {
const decodedToken = await getAuth().verifyIdToken(idToken);
const uid = decodedToken.uid;
const email = decodedToken.email;
const emailVerified = decodedToken.email_verified;
const customClaims = decodedToken; // Includes custom claims
return decodedToken;
} catch (error) {
console.error("Token verification failed:", error);
throw error;
}
}Python:
from firebase_admin import auth
def verify_token(id_token: str):
try:
decoded_token = auth.verify_id_token(id_token)
uid = decoded_token["uid"]
email = decoded_token.get("email")
return decoded_token
except auth.InvalidIdTokenError:
raise Exception("Invalid token")
except auth.ExpiredIdTokenError:
raise Exception("Token expired")In Cloud Functions (Callable)
import { onCall, HttpsError } from "firebase-functions/v2/https";
export const secureFunction = onCall(async (request) => {
// Auth is automatically verified for callable functions
if (!request.auth) {
throw new HttpsError("unauthenticated", "Must be signed in");
}
const uid = request.auth.uid;
const email = request.auth.token.email;
const isAdmin = request.auth.token.admin === true;
return { uid, email, isAdmin };
});In HTTP Functions
import { onRequest } from "firebase-functions/v2/https";
import { getAuth } from "firebase-admin/auth";
export const secureApi = onRequest(async (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
res.status(401).json({ error: "Missing authorization header" });
return;
}
const idToken = authHeader.split("Bearer ")[1];
try {
const decodedToken = await getAuth().verifyIdToken(idToken);
// Token is valid
res.json({ uid: decodedToken.uid });
} catch (error) {
res.status(401).json({ error: "Invalid token" });
}
});Get ID Token (Client)
import { getIdToken } from "firebase/auth";
// Get token for API calls
const idToken = await getIdToken(auth.currentUser);
// Force refresh
const freshToken = await getIdToken(auth.currentUser, true);
// Use in API call
const response = await fetch("/api/secure", {
headers: {
Authorization: `Bearer ${idToken}`
}
});---
Custom Claims
Custom claims add role/permission data to tokens.
Set Claims (Server-side)
import { getAuth } from "firebase-admin/auth";
// Set claims
await getAuth().setCustomUserClaims(uid, {
admin: true,
role: "editor",
accessLevel: 5,
permissions: ["read", "write", "delete"]
});
// Get user with claims
const user = await getAuth().getUser(uid);
console.log(user.customClaims); // { admin: true, role: "editor", ... }Python:
from firebase_admin import auth
# Set claims
auth.set_custom_user_claims(uid, {
"admin": True,
"role": "editor",
"accessLevel": 5
})
# Get user
user = auth.get_user(uid)
print(user.custom_claims)Access Claims (Client)
import { getIdTokenResult } from "firebase/auth";
const tokenResult = await getIdTokenResult(auth.currentUser);
const claims = tokenResult.claims;
if (claims.admin === true) {
console.log("User is admin");
}
// Force refresh to get updated claims
const freshResult = await getIdTokenResult(auth.currentUser, true);Claims in Security Rules
// Firestore
match /admin/{document=**} {
allow read, write: if request.auth.token.admin == true;
}
match /content/{docId} {
allow write: if request.auth.token.role == "editor";
}
// Storage
match /sensitive/{allPaths=**} {
allow read: if request.auth.token.accessLevel >= 5;
}Claim Limits
- Max 1000 bytes total for all claims
- Reserved claims:
acr,amr,at_hash,aud,auth_time,azp,cnf,c_hash,exp,firebase,iat,iss,jti,nbf,nonce,sub
---
Custom Tokens
Create tokens for external auth systems or service-to-service auth.
Create Custom Token
import { getAuth } from "firebase-admin/auth";
// Simple token
const customToken = await getAuth().createCustomToken(uid);
// With additional claims
const customToken = await getAuth().createCustomToken(uid, {
premiumAccount: true,
subscriptionLevel: "gold"
});Python:
from firebase_admin import auth
# Simple token
custom_token = auth.create_custom_token(uid)
# With claims
custom_token = auth.create_custom_token(uid, {
"premiumAccount": True
})Sign In with Custom Token (Client)
import { signInWithCustomToken } from "firebase/auth";
const userCredential = await signInWithCustomToken(auth, customToken);
const user = userCredential.user;---
User Management
Get User
import { getAuth } from "firebase-admin/auth";
// By UID
const user = await getAuth().getUser(uid);
// By email
const user = await getAuth().getUserByEmail(email);
// By phone
const user = await getAuth().getUserByPhoneNumber(phoneNumber);
// Multiple users
const result = await getAuth().getUsers([
{ uid: "uid1" },
{ email: "user@example.com" },
{ phoneNumber: "+1234567890" }
]);Create User
const user = await getAuth().createUser({
email: "user@example.com",
emailVerified: true,
password: "secretPassword",
displayName: "John Doe",
photoURL: "http://example.com/photo.jpg",
disabled: false
});Update User
await getAuth().updateUser(uid, {
email: "newemail@example.com",
displayName: "New Name",
password: "newPassword",
emailVerified: true,
disabled: false
});Delete User
await getAuth().deleteUser(uid);
// Delete multiple
await getAuth().deleteUsers([uid1, uid2, uid3]);List Users
// List all users (paginated)
const listUsersResult = await getAuth().listUsers(1000);
listUsersResult.users.forEach((user) => {
console.log(user.uid, user.email);
});
// Paginate
if (listUsersResult.pageToken) {
const nextPage = await getAuth().listUsers(1000, listUsersResult.pageToken);
}Revoke Refresh Tokens
// Force sign out everywhere
await getAuth().revokeRefreshTokens(uid);
// User must reauthenticatePython:
from firebase_admin import auth
# Get user
user = auth.get_user(uid)
user = auth.get_user_by_email(email)
# Create user
user = auth.create_user(
email="user@example.com",
password="secretPassword",
display_name="John Doe"
)
# Update user
auth.update_user(uid, email="new@example.com")
# Delete user
auth.delete_user(uid)
# List users
for user in auth.list_users().iterate_all():
print(user.uid, user.email)Firebase CLI Commands Reference
Quick reference for Firebase CLI commands organized by service.
Contents
- Authentication
- Project Management
- Initialization
- Deployment
- Emulators
- Cloud Functions
- Firestore
- Hosting
- Storage
---
Authentication
firebase login # Interactive browser login
firebase login --no-localhost # For SSH/headless environments
firebase login:ci # Generate CI token
firebase login:add # Add additional account
firebase login:list # List authorized accounts
firebase login:use <email> # Switch active account
firebase logout # Sign out current accountProject Management
firebase projects:list # List all accessible projects
firebase projects:create <id> # Create new project
firebase use # Show active project
firebase use <project-id> # Switch active project
firebase use --add # Add project alias interactively
firebase use <alias> # Switch to aliased project
firebase open # Open project in browser console
firebase open hosting # Open specific service in console
firebase open functions
firebase open firestore
firebase open authInitialization
firebase init # Interactive init (select features)
firebase init <features> # Init specific features
# Features: firestore, functions, hosting, storage, emulators, database, remoteconfig
# Common combinations
firebase init firestore functions hosting emulators
firebase init hosting # Static site only
firebase init functions # Functions onlyDeployment
# Full deployment
firebase deploy # Deploy all configured features
firebase deploy --only <targets> # Deploy specific targets
firebase deploy --except <targets> # Deploy all except specified
# Target examples
firebase deploy --only hosting
firebase deploy --only functions
firebase deploy --only firestore # Rules + indexes
firebase deploy --only firestore:rules
firebase deploy --only firestore:indexes
firebase deploy --only storage # Storage rules
firebase deploy --only functions:myFunction # Single function
firebase deploy --only functions:func1,func2 # Multiple functions
# Options
firebase deploy --dry-run # Preview without deploying
firebase deploy --force # Skip confirmation prompts
firebase deploy --message "v1.2" # Add deploy message (hosting)Emulators
# Starting emulators
firebase emulators:start # Start all configured
firebase emulators:start --only functions,firestore # Specific emulators
firebase emulators:start --project <id> # With specific project
# Data persistence
firebase emulators:start --import=./data # Import saved data
firebase emulators:start --export-on-exit=./data # Export on shutdown
firebase emulators:start --import=./data --export-on-exit # Both
# Debugging
firebase emulators:start --inspect-functions # Enable debugger (port 9229)
# Running tests
firebase emulators:exec "npm test" # Run command then shutdown
firebase emulators:exec --only firestore "npm test" # With specific emulators
# Emulator info
firebase emulators:export ./backup # Export current data
# Default ports
# Auth: 9099
# Functions: 5001
# Firestore: 8080
# Database: 9000
# Hosting: 5000
# Pub/Sub: 8085
# Storage: 9199
# UI: 4000Cloud Functions
# Logs
firebase functions:log # View recent logs
firebase functions:log --only <function> # Specific function logs
firebase functions:log -n 100 # Last N log entries
# Management
firebase functions:list # List deployed functions
firebase functions:delete <function> # Delete a function
firebase functions:delete <func> --region <r> # Delete in specific region
# Secrets (2nd generation)
firebase functions:secrets:set <NAME> # Set secret (prompts for value)
firebase functions:secrets:get <NAME> # View secret metadata
firebase functions:secrets:access <NAME> # View secret value
firebase functions:secrets:destroy <NAME> # Delete secret
firebase functions:secrets:list # List all secrets
firebase functions:secrets:prune # Remove unused secrets
# Configuration (1st generation - deprecated)
firebase functions:config:set key=value # Set config
firebase functions:config:get # Get all config
firebase functions:config:unset key # Remove configFirestore
# Indexes
firebase firestore:indexes # List indexes
firebase firestore:indexes > indexes.json # Export index config
# Data management
firebase firestore:delete <path> # Delete document
firebase firestore:delete <path> --recursive # Delete collection recursively
firebase firestore:delete <path> -r --force # Skip confirmation
# Rules
firebase deploy --only firestore:rules # Deploy rules onlyHosting
# Deployment
firebase deploy --only hosting # Deploy hosting
firebase hosting:disable # Take site offline
# Preview channels
firebase hosting:channel:create <id> # Create preview channel
firebase hosting:channel:deploy <id> # Deploy to channel
firebase hosting:channel:deploy <id> --expires 7d # With expiration
firebase hosting:channel:list # List channels
firebase hosting:channel:delete <id> # Delete channel
firebase hosting:clone <src>:<ch> <dst>:live # Promote to live
# Rollback
firebase hosting:rollback # Rollback to previous version
# Sites (multi-site hosting)
firebase hosting:sites:list # List sites
firebase hosting:sites:create <id> # Create new site
firebase hosting:sites:delete <id> # Delete site
firebase target:apply hosting <target> <site> # Apply deploy targetStorage
firebase deploy --only storage # Deploy storage rulesUseful Options (Global)
--project <id> # Override active project
--config <file> # Use alternate firebase.json
--debug # Enable debug logging
--json # Output as JSON (for scripting)
--non-interactive # Disable prompts (CI/CD)
--token <token> # Use CI token for auth.firebaserc Project Aliases
{
"projects": {
"default": "my-project-dev",
"staging": "my-project-staging",
"production": "my-project-prod"
}
}Switch with: firebase use staging or firebase use production
Firestore Database Reference
Complete reference for Firestore operations, queries, and data modeling.
Contents
- Data Model
- Initialization
- CRUD Operations
- Queries
- Real-time Listeners
- Transactions & Batches
- Data Modeling Patterns
- Indexes
- Offline Persistence
---
Data Model
Firestore stores data in documents organized into collections:
users (collection)
└── user123 (document)
├── name: "John" # field
├── email: "j@example.com" # field
├── address: { # nested map
│ city: "NYC",
│ zip: "10001"
│ }
└── tags: ["admin", "dev"] # arrayLimits:
- Document size: 1 MB max
- Document path depth: 100 levels max
- Field name: 1,500 bytes max
- Subcollection nesting: unlimited
Initialization
JavaScript/TypeScript (Client SDK)
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);JavaScript/TypeScript (Admin SDK)
import { initializeApp, cert } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
initializeApp({ credential: cert(serviceAccount) });
// Or in Cloud Functions: initializeApp();
const db = getFirestore();Python (Admin SDK)
import firebase_admin
from firebase_admin import credentials, firestore
cred = credentials.Certificate("serviceAccount.json")
firebase_admin.initialize_app(cred)
# Or in Cloud Functions: firebase_admin.initialize_app()
db = firestore.client()---
CRUD Operations
Create / Set Document
JavaScript/TypeScript:
import { doc, setDoc, addDoc, collection } from "firebase/firestore";
// Set with explicit ID
await setDoc(doc(db, "users", "user123"), {
name: "John",
email: "john@example.com",
createdAt: new Date()
});
// Add with auto-generated ID
const docRef = await addDoc(collection(db, "users"), {
name: "Jane",
email: "jane@example.com"
});
console.log("New doc ID:", docRef.id);
// Merge with existing document (partial update, creates if missing)
await setDoc(doc(db, "users", "user123"),
{ lastLogin: new Date() },
{ merge: true }
);Python:
# Set with explicit ID
db.collection("users").document("user123").set({
"name": "John",
"email": "john@example.com",
"createdAt": firestore.SERVER_TIMESTAMP
})
# Add with auto-generated ID
update_time, doc_ref = db.collection("users").add({
"name": "Jane",
"email": "jane@example.com"
})
# Merge
db.collection("users").document("user123").set(
{"lastLogin": firestore.SERVER_TIMESTAMP},
merge=True
)Read Document
JavaScript/TypeScript:
import { doc, getDoc, collection, getDocs } from "firebase/firestore";
// Single document
const docSnap = await getDoc(doc(db, "users", "user123"));
if (docSnap.exists()) {
console.log("Data:", docSnap.data());
console.log("ID:", docSnap.id);
} else {
console.log("Document not found");
}
// All documents in collection
const querySnapshot = await getDocs(collection(db, "users"));
querySnapshot.forEach((doc) => {
console.log(doc.id, "=>", doc.data());
});Python:
# Single document
doc = db.collection("users").document("user123").get()
if doc.exists:
print(f"Data: {doc.to_dict()}")
print(f"ID: {doc.id}")
# All documents
docs = db.collection("users").stream()
for doc in docs:
print(f"{doc.id} => {doc.to_dict()}")Update Document
JavaScript/TypeScript:
import { doc, updateDoc, arrayUnion, arrayRemove, increment, deleteField, serverTimestamp } from "firebase/firestore";
const userRef = doc(db, "users", "user123");
// Simple update
await updateDoc(userRef, {
name: "John Doe",
"address.city": "Los Angeles" // Nested field (dot notation)
});
// Special operations
await updateDoc(userRef, {
tags: arrayUnion("newTag"), // Add to array (if not exists)
oldTags: arrayRemove("deprecated"), // Remove from array
loginCount: increment(1), // Atomic increment
updatedAt: serverTimestamp(), // Server timestamp
deprecatedField: deleteField() // Delete field
});Python:
from google.cloud.firestore_v1 import ArrayUnion, ArrayRemove, Increment, DELETE_FIELD
user_ref = db.collection("users").document("user123")
# Simple update
user_ref.update({
"name": "John Doe",
"address.city": "Los Angeles"
})
# Special operations
user_ref.update({
"tags": ArrayUnion(["newTag"]),
"oldTags": ArrayRemove(["deprecated"]),
"loginCount": Increment(1),
"updatedAt": firestore.SERVER_TIMESTAMP,
"deprecatedField": DELETE_FIELD
})Delete Document
JavaScript/TypeScript:
import { doc, deleteDoc } from "firebase/firestore";
await deleteDoc(doc(db, "users", "user123"));
// Note: Deleting a document does NOT delete subcollections
// Delete subcollections recursively with a Cloud Function or batchPython:
db.collection("users").document("user123").delete()---
Queries
Query Operators
| Operator | Description |
|---|---|
== | Equal to |
!= | Not equal to |
< | Less than |
<= | Less than or equal |
> | Greater than |
>= | Greater than or equal |
array-contains | Array contains value |
array-contains-any | Array contains any of values |
in | Field equals any of values |
not-in | Field doesn't equal any values |
Basic Queries
JavaScript/TypeScript:
import { collection, query, where, orderBy, limit, getDocs, startAfter, endBefore } from "firebase/firestore";
const usersRef = collection(db, "users");
// Simple equality
const q1 = query(usersRef, where("status", "==", "active"));
// Comparison
const q2 = query(usersRef, where("age", ">=", 18));
// Multiple conditions (AND)
const q3 = query(usersRef,
where("status", "==", "active"),
where("age", ">=", 18)
);
// Ordering and limiting
const q4 = query(usersRef,
where("status", "==", "active"),
orderBy("createdAt", "desc"),
limit(10)
);
// Execute query
const snapshot = await getDocs(q4);
snapshot.forEach((doc) => console.log(doc.id, doc.data()));Python:
from google.cloud.firestore_v1 import FieldFilter, Query
users_ref = db.collection("users")
# Simple equality
docs = users_ref.where(filter=FieldFilter("status", "==", "active")).stream()
# Multiple conditions
docs = (users_ref
.where(filter=FieldFilter("status", "==", "active"))
.where(filter=FieldFilter("age", ">=", 18))
.order_by("createdAt", direction=Query.DESCENDING)
.limit(10)
.stream())
for doc in docs:
print(f"{doc.id} => {doc.to_dict()}")Array Queries
// Array contains single value
const q = query(usersRef, where("tags", "array-contains", "admin"));
// Array contains any of values (max 30 values)
const q = query(usersRef, where("tags", "array-contains-any", ["admin", "moderator"]));IN Queries
// Field in list (max 30 values)
const q = query(usersRef, where("status", "in", ["active", "pending"]));
// Field not in list
const q = query(usersRef, where("status", "not-in", ["banned", "deleted"]));OR Queries
import { or, and } from "firebase/firestore";
// OR conditions
const q = query(usersRef,
or(
where("status", "==", "active"),
where("role", "==", "admin")
)
);
// Complex: (status=active AND age>=18) OR (role=admin)
const q = query(usersRef,
or(
and(where("status", "==", "active"), where("age", ">=", 18)),
where("role", "==", "admin")
)
);Collection Group Queries
Query across all subcollections with the same name:
import { collectionGroup, query, where, getDocs } from "firebase/firestore";
// Query all "comments" subcollections across all documents
const q = query(
collectionGroup(db, "comments"),
where("author", "==", "user123")
);
const snapshot = await getDocs(q);Requires index: Collection group queries require a composite index with collection group scope.
Pagination with Cursors
import { query, orderBy, limit, startAfter, getDocs } from "firebase/firestore";
// First page
const first = query(usersRef, orderBy("name"), limit(25));
const firstSnapshot = await getDocs(first);
const lastDoc = firstSnapshot.docs[firstSnapshot.docs.length - 1];
// Next page
const next = query(usersRef,
orderBy("name"),
startAfter(lastDoc), // Start after last document
limit(25)
);
const nextSnapshot = await getDocs(next);
// Other cursor functions:
// startAt(doc) - Start at document (inclusive)
// startAfter(doc) - Start after document (exclusive)
// endAt(doc) - End at document (inclusive)
// endBefore(doc) - End before document (exclusive)---
Real-time Listeners
Document Listener
JavaScript/TypeScript:
import { doc, onSnapshot } from "firebase/firestore";
const unsubscribe = onSnapshot(doc(db, "users", "user123"), (doc) => {
if (doc.exists()) {
console.log("Current data:", doc.data());
}
});
// Stop listening
unsubscribe();Collection/Query Listener
import { collection, query, where, onSnapshot } from "firebase/firestore";
const q = query(collection(db, "users"), where("status", "==", "active"));
const unsubscribe = onSnapshot(q, (snapshot) => {
// Process changes
snapshot.docChanges().forEach((change) => {
if (change.type === "added") {
console.log("New:", change.doc.id, change.doc.data());
}
if (change.type === "modified") {
console.log("Modified:", change.doc.id, change.doc.data());
}
if (change.type === "removed") {
console.log("Removed:", change.doc.id);
}
});
// Or get all current docs
snapshot.forEach((doc) => console.log(doc.id, doc.data()));
});Error Handling
const unsubscribe = onSnapshot(
doc(db, "users", "user123"),
(doc) => { /* handle data */ },
(error) => { console.error("Listen error:", error); }
);Python:
def on_snapshot(doc_snapshot, changes, read_time):
for doc in doc_snapshot:
print(f"Received: {doc.to_dict()}")
doc_watch = db.collection("users").document("user123").on_snapshot(on_snapshot)
# Stop listening
doc_watch.unsubscribe()---
Transactions & Batches
Transactions
Atomic read-then-write operations. All reads must come before writes.
JavaScript/TypeScript:
import { runTransaction, doc } from "firebase/firestore";
try {
await runTransaction(db, async (transaction) => {
const userRef = doc(db, "users", "user123");
const userDoc = await transaction.get(userRef);
if (!userDoc.exists()) {
throw "Document does not exist!";
}
const newBalance = userDoc.data().balance + 100;
transaction.update(userRef, { balance: newBalance });
});
console.log("Transaction succeeded");
} catch (e) {
console.error("Transaction failed:", e);
}Python:
from google.cloud.firestore_v1 import transactional
@transactional
def update_balance(transaction, user_ref, amount):
snapshot = user_ref.get(transaction=transaction)
new_balance = snapshot.get("balance") + amount
transaction.update(user_ref, {"balance": new_balance})
transaction = db.transaction()
user_ref = db.collection("users").document("user123")
update_balance(transaction, user_ref, 100)Batched Writes
Multiple write operations as single atomic unit (no reads).
JavaScript/TypeScript:
import { writeBatch, doc } from "firebase/firestore";
const batch = writeBatch(db);
// Add operations
batch.set(doc(db, "users", "user1"), { name: "User 1" });
batch.update(doc(db, "users", "user2"), { status: "active" });
batch.delete(doc(db, "users", "user3"));
// Commit all at once
await batch.commit();Python:
batch = db.batch()
batch.set(db.collection("users").document("user1"), {"name": "User 1"})
batch.update(db.collection("users").document("user2"), {"status": "active"})
batch.delete(db.collection("users").document("user3"))
batch.commit()Limits:
- Max 500 operations per batch/transaction
- Transactions retry up to 5 times on contention
- Transactions require online connectivity
---
Data Modeling Patterns
Embedding vs References
Embed (denormalize) when:
- Data is read together frequently
- Embedded data rarely changes
- You want single-read performance
// Embedded author info
{
title: "My Post",
content: "...",
author: { // Embedded
id: "user123",
name: "John",
avatar: "url..."
}
}Reference when:
- Data changes frequently
- Many-to-many relationships
- Data is large or unbounded
// Reference only
{
title: "My Post",
content: "...",
authorId: "user123" // Reference - fetch author separately
}Subcollections vs Root Collections
Subcollections for:
- Parent-child relationships
- Querying within a parent context
- Natural hierarchies
posts/{postId}/comments/{commentId}
users/{userId}/orders/{orderId}Root collections for:
- Cross-parent queries
- Many-to-many relationships
- Independent entities
comments (with postId field)
orders (with userId field)Aggregations
Firestore doesn't support aggregation queries. Options:
1. Maintain counters (recommended for counts):
// In transaction or Cloud Function
await updateDoc(doc(db, "stats", "global"), {
userCount: increment(1)
});2. count() for simple counts:
import { getCountFromServer, collection, query, where } from "firebase/firestore";
const q = query(collection(db, "users"), where("active", "==", true));
const snapshot = await getCountFromServer(q);
console.log("Count:", snapshot.data().count);3. Distributed counters for high-write scenarios
---
Indexes
Single-field Indexes
Automatically created for every field. Enable:
- Simple equality/comparison queries
- Ordering by single field
Composite Indexes
Required for queries with multiple fields or collection groups.
firestore.indexes.json:
{
"indexes": [
{
"collectionGroup": "posts",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "status", "order": "ASCENDING" },
{ "fieldPath": "createdAt", "order": "DESCENDING" }
]
},
{
"collectionGroup": "comments",
"queryScope": "COLLECTION_GROUP",
"fields": [
{ "fieldPath": "author", "order": "ASCENDING" }
]
}
],
"fieldOverrides": [
{
"collectionGroup": "posts",
"fieldPath": "content",
"indexes": []
}
]
}Deploy: firebase deploy --only firestore:indexes
Tip: Run query, error message includes link to create required index automatically.
---
Offline Persistence
Enable (Web)
import { enableIndexedDbPersistence, getFirestore } from "firebase/firestore";
const db = getFirestore();
await enableIndexedDbPersistence(db);
// Now queries work offlineEnable (Mobile)
Enabled by default on iOS/Android.
Check Pending Writes
import { onSnapshot } from "firebase/firestore";
onSnapshot(doc(db, "users", "user123"), (doc) => {
const source = doc.metadata.hasPendingWrites ? "Local" : "Server";
console.log(source, "data:", doc.data());
});Wait for Server Sync
import { waitForPendingWrites } from "firebase/firestore";
await waitForPendingWrites(db);
console.log("All pending writes synced to server");Cloud Functions Patterns Reference
Error handling, optimization, secrets, testing, and best practices.
Contents
- Error Handling
- Secrets Management
- Global Options
- Performance Optimization
- Idempotency
- Retry Behavior
- Logging
- Testing with Emulators
---
Error Handling
Callable Functions
import { onCall, HttpsError } from "firebase-functions/v2/https";
import { logger } from "firebase-functions";
export const safeCallable = onCall(async (request) => {
try {
// Validate input
if (!request.data?.id) {
throw new HttpsError("invalid-argument", "ID is required");
}
// Check authentication
if (!request.auth) {
throw new HttpsError("unauthenticated", "Must be logged in");
}
// Check permissions
if (!request.auth.token.admin) {
throw new HttpsError("permission-denied", "Admin access required");
}
// Business logic
const result = await processData(request.data);
return { success: true, result };
} catch (error) {
// Re-throw HttpsError as-is
if (error instanceof HttpsError) {
throw error;
}
// Log unexpected errors
logger.error("Unexpected error:", error);
// Return generic error to client
throw new HttpsError("internal", "An unexpected error occurred");
}
});Background Functions (Event Triggers)
import { onDocumentCreated } from "firebase-functions/v2/firestore";
import { logger } from "firebase-functions";
export const processDocument = onDocumentCreated(
"orders/{orderId}",
async (event) => {
try {
const data = event.data?.data();
if (!data) {
logger.warn("No data in document");
return; // Don't retry - data won't appear
}
await processOrder(data);
logger.info("Order processed successfully", { orderId: event.params.orderId });
} catch (error) {
// Log error with context
logger.error("Failed to process order", {
orderId: event.params.orderId,
error: error.message,
stack: error.stack
});
// Throwing will trigger retry (if retries enabled)
throw error;
}
}
);Python Error Handling
from firebase_functions import https_fn
from firebase_functions.firestore_fn import on_document_created, Event, DocumentSnapshot
import logging
@https_fn.on_call()
def safe_callable(req: https_fn.CallableRequest):
try:
if not req.data.get("id"):
raise https_fn.HttpsError(
code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT,
message="ID is required"
)
if not req.auth:
raise https_fn.HttpsError(
code=https_fn.FunctionsErrorCode.UNAUTHENTICATED,
message="Must be logged in"
)
result = process_data(req.data)
return {"success": True, "result": result}
except https_fn.HttpsError:
raise
except Exception as e:
logging.exception("Unexpected error")
raise https_fn.HttpsError(
code=https_fn.FunctionsErrorCode.INTERNAL,
message="An unexpected error occurred"
)---
Secrets Management
2nd Generation (Recommended)
Define and use secrets:
import { defineSecret } from "firebase-functions/params";
import { onRequest, onCall } from "firebase-functions/v2/https";
// Define secrets
const apiKey = defineSecret("API_KEY");
const dbPassword = defineSecret("DB_PASSWORD");
// HTTP function with secrets
export const secureApi = onRequest(
{ secrets: [apiKey, dbPassword] },
async (req, res) => {
const key = apiKey.value();
const password = dbPassword.value();
// Use secrets...
res.json({ status: "ok" });
}
);
// Callable with secrets
export const secureCallable = onCall(
{ secrets: [apiKey] },
async (request) => {
const key = apiKey.value();
return { success: true };
}
);CLI commands:
firebase functions:secrets:set API_KEY # Set secret (prompts for value)
firebase functions:secrets:get API_KEY # View metadata
firebase functions:secrets:access API_KEY # View value
firebase functions:secrets:destroy API_KEY # Delete
firebase functions:secrets:list # List allPython Secrets
from firebase_functions import https_fn
from firebase_functions.params import SecretParam
API_KEY = SecretParam("API_KEY")
@https_fn.on_request(secrets=[API_KEY])
def secure_api(req: https_fn.Request) -> https_fn.Response:
key = API_KEY.value
# Use key...
return https_fn.Response("OK")Environment Parameters (Non-secret)
import { defineString, defineInt, defineBool } from "firebase-functions/params";
const apiUrl = defineString("API_URL");
const maxRetries = defineInt("MAX_RETRIES", { default: 3 });
const debugMode = defineBool("DEBUG_MODE", { default: false });
export const myFunction = onRequest((req, res) => {
const url = apiUrl.value();
const retries = maxRetries.value();
const debug = debugMode.value();
res.json({ url, retries, debug });
});Set via .env files:
# .env.local (development)
API_URL=http://localhost:3000
DEBUG_MODE=true
# .env.production
API_URL=https://api.production.com
DEBUG_MODE=false---
Global Options
Set Defaults for All Functions
import { setGlobalOptions } from "firebase-functions/v2";
setGlobalOptions({
region: "us-central1",
memory: "512MiB",
timeoutSeconds: 120,
maxInstances: 10,
minInstances: 0,
concurrency: 80
});Per-Function Override
export const heavyFunction = onRequest(
{
memory: "4GiB",
timeoutSeconds: 540,
maxInstances: 50,
minInstances: 1, // Keep warm
concurrency: 500,
cpu: 2
},
(req, res) => {
res.send("Heavy processing complete");
}
);Available Options
| Option | Description | Default |
|---|---|---|
region | Deployment region | us-central1 |
memory | Memory allocation | 256MiB |
timeoutSeconds | Max execution time | 60 (HTTP: 60, Event: 540) |
minInstances | Minimum warm instances | 0 |
maxInstances | Maximum instances | 100 |
concurrency | Requests per instance | 80 |
cpu | vCPU count | 1 |
vpcConnector | VPC connector | - |
ingressSettings | Ingress rules | ALLOW_ALL |
labels | Custom labels | {} |
Memory/CPU Combinations
| Memory | Default CPU |
|---|---|
| 128MiB - 512MiB | 0.083 |
| 1GiB | 0.5 |
| 2GiB | 1 |
| 4GiB | 2 |
| 8GiB+ | 2 (can specify up to 4) |
---
Performance Optimization
Reduce Cold Starts
1. Use minInstances:
export const lowLatencyApi = onRequest(
{ minInstances: 1 },
(req, res) => res.send("Fast!")
);2. Lazy-load dependencies:
// ❌ Bad: Loaded on every cold start
import * as heavyLib from "heavy-library";
// ✅ Good: Loaded only when needed
let heavyLib: typeof import("heavy-library") | null = null;
async function getHeavyLib() {
if (!heavyLib) {
heavyLib = await import("heavy-library");
}
return heavyLib;
}3. Reuse connections with global scope:
import { initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
// Initialize once, reuse across invocations
const app = initializeApp();
const db = getFirestore();
export const myFunction = onRequest(async (req, res) => {
// db is reused
const doc = await db.collection("users").doc("123").get();
res.json(doc.data());
});4. Use concurrency (2nd gen):
export const highThroughput = onRequest(
{ concurrency: 500 }, // Handle 500 concurrent requests per instance
(req, res) => res.send("OK")
);Optimize Memory Usage
// Stream large files instead of loading into memory
import { getStorage } from "firebase-admin/storage";
export const processLargeFile = onObjectFinalized(async (event) => {
const bucket = getStorage().bucket(event.data.bucket);
const file = bucket.file(event.data.name);
// Stream instead of download
const stream = file.createReadStream();
for await (const chunk of stream) {
// Process chunk by chunk
}
});---
Idempotency
Background functions may execute multiple times. Design for idempotency.
Use Event ID for Deduplication
import { onDocumentCreated } from "firebase-functions/v2/firestore";
import { getFirestore } from "firebase-admin/firestore";
const db = getFirestore();
export const processOnce = onDocumentCreated(
"orders/{orderId}",
async (event) => {
const eventId = event.id;
const lockRef = db.collection("processedEvents").doc(eventId);
// Atomic check-and-set
try {
await db.runTransaction(async (transaction) => {
const lockDoc = await transaction.get(lockRef);
if (lockDoc.exists) {
console.log("Already processed, skipping");
return;
}
// Mark as processed
transaction.set(lockRef, {
processedAt: new Date(),
orderId: event.params.orderId
});
// Do actual processing
await processOrder(event.data?.data());
});
} catch (error) {
console.error("Transaction failed:", error);
throw error;
}
}
);Idempotent Operations
// ❌ Not idempotent - will double-charge
await chargeCustomer(customerId, amount);
// ✅ Idempotent - uses idempotency key
await chargeCustomer(customerId, amount, { idempotencyKey: event.id });
// ❌ Not idempotent - counter will be wrong
await updateDoc(docRef, { count: increment(1) });
// ✅ Idempotent - set to absolute value
await setDoc(docRef, { count: newTotal });---
Retry Behavior
Event-triggered Functions
By default, background functions retry on failure:
- Firestore triggers: Retry for up to 7 days
- Pub/Sub triggers: Configurable, default exponential backoff
- Storage triggers: Retry for up to 7 days
Disable retries:
export const noRetry = onDocumentCreated(
{
document: "orders/{orderId}",
retry: false
},
async (event) => {
// Will not retry on failure
}
);Handle Retries Gracefully
export const withRetryHandling = onDocumentCreated(
"orders/{orderId}",
async (event) => {
const eventTime = new Date(event.time);
const now = new Date();
const ageMinutes = (now.getTime() - eventTime.getTime()) / 60000;
// Give up after 30 minutes of retries
if (ageMinutes > 30) {
console.warn("Event too old, giving up", { age: ageMinutes });
return; // Return without throwing to stop retries
}
try {
await processOrder(event.data?.data());
} catch (error) {
if (isTransient(error)) {
throw error; // Retry
} else {
console.error("Permanent failure, not retrying", error);
return; // Don't retry
}
}
}
);
function isTransient(error: any): boolean {
// Network errors, rate limits, etc.
return error.code === "UNAVAILABLE" ||
error.code === "DEADLINE_EXCEEDED" ||
error.code === "RESOURCE_EXHAUSTED";
}---
Logging
Structured Logging
import { logger } from "firebase-functions";
export const myFunction = onRequest((req, res) => {
// Simple logs
logger.debug("Debug message");
logger.info("Info message");
logger.warn("Warning message");
logger.error("Error message");
// Structured logging (recommended)
logger.info("Processing request", {
path: req.path,
method: req.method,
userId: req.headers["x-user-id"]
});
// With severity
logger.write({
severity: "INFO",
message: "Custom log entry",
customField: "value"
});
res.send("OK");
});Python Logging
import logging
# Cloud Functions picks up standard logging
logging.info("Info message")
logging.warning("Warning message")
logging.error("Error message")
# With structured data
logging.info("Processing request", extra={
"path": req.path,
"method": req.method
})View Logs
firebase functions:log # All functions
firebase functions:log --only myFunction # Specific function
firebase functions:log -n 100 # Last 100 entries
# In GCP Console: Logging > Logs Explorer
# Filter: resource.type="cloud_function"---
Testing with Emulators
Setup
firebase emulators:start --only functions,firestoreUnit Testing (TypeScript/Jest)
import { describe, it, beforeAll, afterAll } from "@jest/globals";
import {
initializeTestEnvironment,
RulesTestEnvironment
} from "@firebase/rules-unit-testing";
let testEnv: RulesTestEnvironment;
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: "demo-test",
firestore: {
host: "localhost",
port: 8080
}
});
});
afterAll(async () => {
await testEnv.cleanup();
});
describe("Cloud Functions", () => {
it("should create profile on user creation", async () => {
const db = testEnv.unauthenticatedContext().firestore();
// Trigger function by creating user document
await db.collection("users").doc("user123").set({
name: "Test User",
email: "test@example.com"
});
// Wait for function to process
await new Promise((resolve) => setTimeout(resolve, 2000));
// Verify profile was created
const profile = await db.collection("profiles").doc("user123").get();
expect(profile.exists).toBe(true);
});
});Test Callable Functions
import { getFunctions, httpsCallable, connectFunctionsEmulator } from "firebase/functions";
const functions = getFunctions();
connectFunctionsEmulator(functions, "localhost", 5001);
const myCallable = httpsCallable(functions, "myCallable");
test("callable function works", async () => {
const result = await myCallable({ input: "test" });
expect(result.data.success).toBe(true);
});Run Tests
# Start emulators and run tests
firebase emulators:exec "npm test"
# With specific emulators
firebase emulators:exec --only functions,firestore "npm test"App Check Integration
App Check protects your backend resources from abuse by verifying requests come from legitimate apps.
Enable App Check for Callable Functions
import { onCall, HttpsError } from "firebase-functions/v2/https";
export const protectedFunction = onCall(
{
enforceAppCheck: true, // Reject requests without valid App Check token
consumeAppCheckToken: true // Prevent token replay attacks
},
async (request) => {
// request.app contains App Check token info
if (!request.app) {
throw new HttpsError("failed-precondition", "App Check required");
}
return { data: "Protected data" };
}
);Verify App Check in HTTP Functions
import { onRequest } from "firebase-functions/v2/https";
import { getAppCheck } from "firebase-admin/app-check";
export const protectedEndpoint = onRequest(async (req, res) => {
const appCheckToken = req.header("X-Firebase-AppCheck");
if (!appCheckToken) {
res.status(401).json({ error: "Missing App Check token" });
return;
}
try {
await getAppCheck().verifyToken(appCheckToken);
res.json({ data: "Protected data" });
} catch (error) {
res.status(401).json({ error: "Invalid App Check token" });
}
});Client-Side Setup (Web)
import { initializeAppCheck, ReCaptchaV3Provider } from "firebase/app-check";
const appCheck = initializeAppCheck(app, {
provider: new ReCaptchaV3Provider("YOUR_RECAPTCHA_SITE_KEY"),
isTokenAutoRefreshEnabled: true
});Debug Mode for Development
// Enable debug mode for local development
if (process.env.NODE_ENV === "development") {
(self as any).FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}Best Practices
1. Enforce in production: Always set enforceAppCheck: true for sensitive endpoints 2. Consume tokens: Use consumeAppCheckToken: true to prevent replay attacks 3. Gradual rollout: Monitor App Check metrics before enforcing 4. Debug tokens: Use debug tokens for development and CI/CD pipelines
Cloud Functions Triggers Reference
Complete reference for all Cloud Functions trigger types with 1st gen, 2nd gen, TypeScript, and Python examples.
Contents
- Generation Comparison
- Firestore Triggers
- Authentication Triggers
- Storage Triggers
- HTTP Triggers
- Callable Functions
- Scheduled Functions
- Pub/Sub Triggers
---
Generation Comparison
| Feature | 1st Generation | 2nd Generation |
|---|---|---|
| Concurrency | 1 request/instance | Up to 1000/instance |
| HTTP Timeout | 9 minutes | 60 minutes |
| Memory | Up to 8 GB | Up to 32 GB |
| vCPU | Tied to memory | Configurable (up to 4) |
| Min instances | Yes | Yes |
| Secrets | Config (deprecated) | Secret Manager |
| Auth triggers | Full support | Blocking only |
| Built on | Cloud Functions | Cloud Run + Eventarc |
Recommendation: Use 2nd gen for new projects. Use 1st gen only for auth onCreate/onDelete triggers.
---
Firestore Triggers
2nd Generation (Recommended)
TypeScript:
import {
onDocumentCreated,
onDocumentUpdated,
onDocumentDeleted,
onDocumentWritten
} from "firebase-functions/v2/firestore";
import { initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
initializeApp();
const db = getFirestore();
// Document created
export const onUserCreated = onDocumentCreated(
"users/{userId}",
async (event) => {
const snapshot = event.data;
if (!snapshot) return;
const data = snapshot.data();
const userId = event.params.userId;
console.log(`New user: ${userId}`, data);
// Example: Create related document
await db.collection("profiles").doc(userId).set({
userId,
createdAt: new Date()
});
}
);
// Document updated
export const onUserUpdated = onDocumentUpdated(
"users/{userId}",
(event) => {
const before = event.data?.before.data();
const after = event.data?.after.data();
const userId = event.params.userId;
if (before?.email !== after?.email) {
console.log(`Email changed for ${userId}: ${before?.email} → ${after?.email}`);
}
}
);
// Document deleted
export const onUserDeleted = onDocumentDeleted(
"users/{userId}",
async (event) => {
const userId = event.params.userId;
// Cleanup related data
await db.collection("profiles").doc(userId).delete();
}
);
// Any write (create, update, delete)
export const onUserWrite = onDocumentWritten(
"users/{userId}",
(event) => {
const before = event.data?.before.exists ? event.data.before.data() : null;
const after = event.data?.after.exists ? event.data.after.data() : null;
if (!before && after) console.log("Created");
else if (before && !after) console.log("Deleted");
else console.log("Updated");
}
);
// With options
export const onOrderCreated = onDocumentCreated(
{
document: "orders/{orderId}",
region: "us-central1",
memory: "1GiB",
timeoutSeconds: 300
},
async (event) => {
// Process order
}
);Python:
from firebase_functions.firestore_fn import (
on_document_created,
on_document_updated,
on_document_deleted,
on_document_written,
Event,
Change,
DocumentSnapshot
)
from firebase_admin import initialize_app, firestore
initialize_app()
db = firestore.client()
@on_document_created(document="users/{userId}")
def on_user_created(event: Event[DocumentSnapshot]) -> None:
if not event.data:
return
data = event.data.to_dict()
user_id = event.params["userId"]
print(f"New user: {user_id}, data: {data}")
@on_document_updated(document="users/{userId}")
def on_user_updated(event: Event[Change[DocumentSnapshot]]) -> None:
before = event.data.before.to_dict() if event.data.before.exists else {}
after = event.data.after.to_dict() if event.data.after.exists else {}
user_id = event.params["userId"]
print(f"User {user_id} updated: {before} → {after}")
@on_document_deleted(document="users/{userId}")
def on_user_deleted(event: Event[DocumentSnapshot]) -> None:
user_id = event.params["userId"]
print(f"User deleted: {user_id}")1st Generation
import * as functions from "firebase-functions/v1";
import * as admin from "firebase-admin";
admin.initializeApp();
export const onUserCreatedV1 = functions.firestore
.document("users/{userId}")
.onCreate((snapshot, context) => {
const data = snapshot.data();
const userId = context.params.userId;
console.log(`New user: ${userId}`, data);
return null;
});
export const onUserUpdatedV1 = functions.firestore
.document("users/{userId}")
.onUpdate((change, context) => {
const before = change.before.data();
const after = change.after.data();
return null;
});
export const onUserDeletedV1 = functions.firestore
.document("users/{userId}")
.onDelete((snapshot, context) => {
return null;
});
export const onUserWriteV1 = functions.firestore
.document("users/{userId}")
.onWrite((change, context) => {
const before = change.before.exists ? change.before.data() : null;
const after = change.after.exists ? change.after.data() : null;
return null;
});
// With region
export const onOrderCreatedV1 = functions
.region("europe-west1")
.firestore
.document("orders/{orderId}")
.onCreate((snapshot, context) => {
return null;
});---
Authentication Triggers
Basic Auth Triggers (1st Gen Only)
import * as functions from "firebase-functions/v1";
import * as admin from "firebase-admin";
admin.initializeApp();
// User created
export const onUserCreate = functions.auth.user().onCreate((user) => {
console.log(`New user: ${user.uid}`);
console.log(`Email: ${user.email}`);
console.log(`Display name: ${user.displayName}`);
console.log(`Photo URL: ${user.photoURL}`);
console.log(`Provider: ${user.providerData}`);
// Create user profile
return admin.firestore().collection("profiles").doc(user.uid).set({
email: user.email,
createdAt: admin.firestore.FieldValue.serverTimestamp()
});
});
// User deleted
export const onUserDelete = functions.auth.user().onDelete((user) => {
console.log(`User deleted: ${user.uid}`);
// Cleanup user data
return admin.firestore().collection("profiles").doc(user.uid).delete();
});Blocking Functions (2nd Gen)
Intercept and modify authentication before completion:
TypeScript:
import { beforeUserCreated, beforeUserSignedIn } from "firebase-functions/v2/identity";
import { HttpsError } from "firebase-functions/v2/https";
// Block or modify user creation
export const validateNewUser = beforeUserCreated((event) => {
const user = event.data;
// Block non-company emails
if (!user.email?.endsWith("@company.com")) {
throw new HttpsError("invalid-argument", "Only company emails allowed");
}
// Return modifications
return {
displayName: user.displayName?.toUpperCase(),
customClaims: {
role: "employee",
department: "general"
}
};
});
// Block or modify sign-in
export const validateSignIn = beforeUserSignedIn((event) => {
const user = event.data;
// Check if user is banned
if (user.customClaims?.banned) {
throw new HttpsError("permission-denied", "Account is suspended");
}
return {
sessionClaims: {
signInTime: new Date().toISOString()
}
};
});Python:
from firebase_functions.identity_fn import (
before_user_created,
before_user_signed_in,
AuthBlockingEvent
)
from firebase_functions.https_fn import HttpsError
@before_user_created()
def validate_new_user(event: AuthBlockingEvent) -> dict | None:
user = event.data
if not user.email or not user.email.endswith("@company.com"):
raise HttpsError(
code="invalid-argument",
message="Only company emails allowed"
)
return {
"customClaims": {"role": "employee"}
}---
Storage Triggers
2nd Generation
TypeScript:
import {
onObjectFinalized,
onObjectDeleted,
onObjectArchived,
onObjectMetadataUpdated
} from "firebase-functions/v2/storage";
import { getStorage } from "firebase-admin/storage";
import * as path from "path";
// Object uploaded/overwritten (finalized)
export const processUpload = onObjectFinalized(
{ cpu: 2, memory: "2GiB" },
async (event) => {
const filePath = event.data.name; // Full path
const bucket = event.data.bucket; // Bucket name
const contentType = event.data.contentType;
const size = event.data.size;
const metadata = event.data.metadata; // Custom metadata
console.log(`File uploaded: ${filePath}`);
console.log(`Content type: ${contentType}`);
console.log(`Size: ${size} bytes`);
// Skip if not an image
if (!contentType?.startsWith("image/")) {
console.log("Not an image, skipping");
return;
}
// Skip if already a thumbnail
const fileName = path.basename(filePath);
if (fileName.startsWith("thumb_")) {
console.log("Already a thumbnail, skipping");
return;
}
// Process image
const storage = getStorage().bucket(bucket);
const file = storage.file(filePath);
// ... generate thumbnail
}
);
// Object deleted
export const onFileDeleted = onObjectDeleted((event) => {
console.log(`File deleted: ${event.data.name}`);
});
// Specific bucket
export const onBackupUploaded = onObjectFinalized(
{ bucket: "my-backup-bucket" },
(event) => {
console.log(`Backup uploaded: ${event.data.name}`);
}
);Python:
from firebase_functions import storage_fn
from firebase_admin import storage
import pathlib
@storage_fn.on_object_finalized()
def process_upload(event: storage_fn.CloudEvent[storage_fn.StorageObjectData]) -> None:
file_path = pathlib.PurePath(event.data.name)
content_type = event.data.content_type
bucket_name = event.data.bucket
print(f"File uploaded: {file_path}")
print(f"Content type: {content_type}")
if not content_type or not content_type.startswith("image/"):
print("Not an image, skipping")
return
if file_path.name.startswith("thumb_"):
print("Already a thumbnail, skipping")
return
# Process image
bucket = storage.bucket(bucket_name)
blob = bucket.blob(str(file_path))
# ... process1st Generation
import * as functions from "firebase-functions/v1";
export const processUploadV1 = functions.storage
.object()
.onFinalize((object) => {
const filePath = object.name;
const contentType = object.contentType;
return null;
});
// Specific bucket
export const onBackupV1 = functions.storage
.bucket("my-backup-bucket")
.object()
.onFinalize((object) => {
return null;
});---
HTTP Triggers
2nd Generation
TypeScript:
import { onRequest } from "firebase-functions/v2/https";
import * as express from "express";
// Simple HTTP function
export const helloWorld = onRequest((req, res) => {
res.send("Hello World!");
});
// With CORS
export const api = onRequest({ cors: true }, (req, res) => {
res.json({ message: "CORS enabled" });
});
// With specific origins
export const secureApi = onRequest(
{ cors: ["https://myapp.com", "https://admin.myapp.com"] },
(req, res) => {
res.json({ message: "Secure API" });
}
);
// With options
export const heavyApi = onRequest(
{
region: "us-central1",
memory: "4GiB",
timeoutSeconds: 540,
minInstances: 1, // Keep warm
maxInstances: 100,
concurrency: 500
},
(req, res) => {
res.json({ status: "ok" });
}
);
// Express app
const app = express();
app.get("/users/:id", (req, res) => {
res.json({ userId: req.params.id });
});
app.post("/users", (req, res) => {
res.json({ created: true, data: req.body });
});
export const expressApi = onRequest(app);Python:
from firebase_functions import https_fn, options
@https_fn.on_request()
def hello_world(req: https_fn.Request) -> https_fn.Response:
return https_fn.Response("Hello World!")
@https_fn.on_request(
cors=options.CorsOptions(
cors_origins=["*"],
cors_methods=["GET", "POST"]
)
)
def cors_enabled(req: https_fn.Request) -> https_fn.Response:
import json
return https_fn.Response(
json.dumps({"message": "CORS enabled"}),
content_type="application/json"
)
@https_fn.on_request(
memory=options.MemoryOption.GB_1,
timeout_sec=300
)
def heavy_processing(req: https_fn.Request) -> https_fn.Response:
# Process...
return https_fn.Response("Done")---
Callable Functions
Type-safe client SDK with automatic authentication.
2nd Generation
TypeScript:
import { onCall, HttpsError } from "firebase-functions/v2/https";
export const processOrder = onCall(async (request) => {
// Input validation
const { orderId, items } = request.data;
if (!orderId || !items?.length) {
throw new HttpsError("invalid-argument", "Missing orderId or items");
}
// Authentication check
if (!request.auth) {
throw new HttpsError("unauthenticated", "Must be logged in");
}
const uid = request.auth.uid;
const email = request.auth.token.email;
// Custom claims check
if (request.auth.token.role !== "admin") {
throw new HttpsError("permission-denied", "Admin access required");
}
// Process and return
return { success: true, orderId, processedBy: uid };
});
// With options
export const heavyCallable = onCall(
{
memory: "2GiB",
timeoutSeconds: 300,
enforceAppCheck: true // Require App Check
},
async (request) => {
return { status: "processed" };
}
);Python:
from firebase_functions import https_fn
from typing import Any
@https_fn.on_call()
def process_order(req: https_fn.CallableRequest) -> Any:
# Authentication check
if not req.auth:
raise https_fn.HttpsError(
code=https_fn.FunctionsErrorCode.UNAUTHENTICATED,
message="Must be logged in"
)
# Input validation
order_id = req.data.get("orderId")
if not order_id:
raise https_fn.HttpsError(
code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT,
message="Missing orderId"
)
return {
"success": True,
"orderId": order_id,
"uid": req.auth.uid
}HttpsError Codes
| Code | HTTP | Description |
|---|---|---|
ok | 200 | Success |
invalid-argument | 400 | Invalid input |
failed-precondition | 400 | State not valid for operation |
out-of-range | 400 | Value out of range |
unauthenticated | 401 | Not authenticated |
permission-denied | 403 | No permission |
not-found | 404 | Resource not found |
already-exists | 409 | Resource already exists |
resource-exhausted | 429 | Quota exceeded |
cancelled | 499 | Operation cancelled |
internal | 500 | Internal error |
unimplemented | 501 | Not implemented |
unavailable | 503 | Service unavailable |
deadline-exceeded | 504 | Timeout |
Client SDK Call
import { getFunctions, httpsCallable } from "firebase/functions";
const functions = getFunctions();
const processOrder = httpsCallable(functions, "processOrder");
try {
const result = await processOrder({ orderId: "123", items: ["a", "b"] });
console.log(result.data);
} catch (error) {
console.error(error.code, error.message);
}---
Scheduled Functions
2nd Generation
TypeScript:
import { onSchedule } from "firebase-functions/v2/scheduler";
import { getFirestore } from "firebase-admin/firestore";
// Every 5 minutes
export const frequentTask = onSchedule("every 5 minutes", async (event) => {
console.log("Running frequent task...");
});
// Cron syntax
export const dailyCleanup = onSchedule(
{
schedule: "0 2 * * *", // 2 AM daily
timeZone: "America/New_York",
timeoutSeconds: 1800,
memory: "2GiB"
},
async (event) => {
const db = getFirestore();
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 30);
const snapshot = await db.collection("logs")
.where("createdAt", "<", cutoff)
.limit(500)
.get();
const batch = db.batch();
snapshot.docs.forEach((doc) => batch.delete(doc.ref));
await batch.commit();
console.log(`Deleted ${snapshot.size} old logs`);
}
);
// Weekly report
export const weeklyReport = onSchedule(
{
schedule: "0 9 * * 1", // Monday 9 AM
timeZone: "America/Los_Angeles"
},
async (event) => {
console.log("Generating weekly report...");
}
);Python:
from firebase_functions import scheduler_fn
@scheduler_fn.on_schedule(schedule="every 5 minutes")
def frequent_task(event: scheduler_fn.ScheduledEvent) -> None:
print("Running frequent task...")
@scheduler_fn.on_schedule(
schedule="0 2 * * *",
timezone="America/New_York"
)
def daily_cleanup(event: scheduler_fn.ScheduledEvent) -> None:
print("Running daily cleanup...")Schedule Syntax
App Engine style:
every 5 minutesevery 24 hoursevery monday 09:00
Cron syntax: minute hour day month weekday
0 * * * *- Every hour0 2 * * *- Daily at 2 AM0 9 * * 1- Every Monday at 9 AM0 0 1 * *- First of every month*/15 * * * *- Every 15 minutes
---
Pub/Sub Triggers
2nd Generation
TypeScript:
import { onMessagePublished } from "firebase-functions/v2/pubsub";
export const processPubSub = onMessagePublished(
"my-topic",
async (event) => {
// Parse message
const message = event.data.message;
const data = message.json; // Parsed JSON
const rawData = message.data; // Base64 string
const attributes = message.attributes;
const messageId = message.messageId;
const publishTime = message.publishTime;
console.log("Message:", data);
console.log("Attributes:", attributes);
}
);
// With options
export const heavyPubSub = onMessagePublished(
{
topic: "heavy-processing",
memory: "4GiB",
timeoutSeconds: 540
},
async (event) => {
// Process...
}
);Python:
from firebase_functions import pubsub_fn
import json
@pubsub_fn.on_message_published(topic="my-topic")
def process_pubsub(event: pubsub_fn.CloudEvent[pubsub_fn.MessagePublishedData]) -> None:
message = event.data.message
# Parse JSON data
if message.data:
data = json.loads(base64.b64decode(message.data).decode())
print(f"Message data: {data}")
# Attributes
if message.attributes:
print(f"Attributes: {message.attributes}")1st Generation
import * as functions from "firebase-functions/v1";
export const processPubSubV1 = functions.pubsub
.topic("my-topic")
.onPublish((message, context) => {
const data = message.json;
const attributes = message.attributes;
return null;
});GCP Integration Reference
Firebase-GCP relationship, IAM, monitoring, secrets, and GCP services.
Contents
- Firebase and GCP
- Service Accounts
- IAM Roles
- Secret Manager
- Cloud Logging
- Cloud Monitoring
- BigQuery Export
- Other GCP Services
---
Firebase and GCP
Firebase projects are GCP projects with Firebase services enabled.
Shared resources:
- Project ID
- Billing account
- IAM permissions
- Service accounts
- APIs and services
- Cloud Console access
Access:
- Firebase Console: console.firebase.google.com
- GCP Console: console.cloud.google.com
- Same project, different views
---
Service Accounts
Default Service Accounts
| Service Account | Purpose |
|---|---|
PROJECT_ID@appspot.gserviceaccount.com | App Engine, Cloud Functions default |
firebase-adminsdk-xxxxx@PROJECT_ID.iam.gserviceaccount.com | Admin SDK operations |
PROJECT_NUMBER@cloudbuild.gserviceaccount.com | Cloud Build |
PROJECT_NUMBER-compute@developer.gserviceaccount.com | Compute Engine default |
Create Service Account
# Create
gcloud iam service-accounts create my-service-account \
--display-name="My Service Account" \
--project=PROJECT_ID
# Grant roles
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:my-service-account@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/datastore.user"
# Create key (for local development)
gcloud iam service-accounts keys create key.json \
--iam-account=my-service-account@PROJECT_ID.iam.gserviceaccount.comUse Service Account in Code
// Local development with key file
import { initializeApp, cert } from "firebase-admin/app";
const serviceAccount = require("./key.json");
initializeApp({
credential: cert(serviceAccount)
});
// In Cloud Functions (automatic)
initializeApp(); // Uses default credentials# Local development
import firebase_admin
from firebase_admin import credentials
cred = credentials.Certificate("key.json")
firebase_admin.initialize_app(cred)
# In Cloud Functions (automatic)
firebase_admin.initialize_app()Application Default Credentials
# Set for local development
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/key.json"
# Or use gcloud
gcloud auth application-default login---
IAM Roles
Firebase-specific Roles
| Role | Description |
|---|---|
roles/firebase.admin | Full Firebase access |
roles/firebase.viewer | Read-only Firebase access |
roles/firebase.developAdmin | Deploy and develop |
roles/firebase.analyticsViewer | View Analytics |
roles/firebase.growthViewer | View A/B Testing, Remote Config |
roles/firebase.qualityViewer | View Crashlytics, Performance |
Cloud Functions Roles
| Role | Description |
|---|---|
roles/cloudfunctions.admin | Full functions access |
roles/cloudfunctions.developer | Deploy and update |
roles/cloudfunctions.invoker | Invoke HTTP functions |
roles/cloudfunctions.viewer | View only |
Firestore Roles
| Role | Description |
|---|---|
roles/datastore.owner | Full Firestore access |
roles/datastore.user | Read/write data |
roles/datastore.viewer | Read-only |
roles/datastore.indexAdmin | Manage indexes |
Storage Roles
| Role | Description |
|---|---|
roles/storage.admin | Full Storage access |
roles/storage.objectAdmin | Manage objects |
roles/storage.objectCreator | Create objects |
roles/storage.objectViewer | View objects |
Grant Roles
# To user
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="user:email@example.com" \
--role="roles/firebase.admin"
# To service account
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:sa@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/datastore.user"
# View current bindings
gcloud projects get-iam-policy PROJECT_ID---
Secret Manager
Via Firebase CLI (Recommended for Functions)
# Set secret
firebase functions:secrets:set API_KEY
# Get metadata
firebase functions:secrets:get API_KEY
# Access value
firebase functions:secrets:access API_KEY
# List all
firebase functions:secrets:list
# Delete
firebase functions:secrets:destroy API_KEYVia GCP (Direct)
# Create secret
echo -n "secret-value" | gcloud secrets create API_KEY --data-file=-
# Add version
echo -n "new-value" | gcloud secrets versions add API_KEY --data-file=-
# Access latest version
gcloud secrets versions access latest --secret=API_KEY
# List secrets
gcloud secrets list
# Delete
gcloud secrets delete API_KEYUse in Cloud Functions
import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/v2/https";
const apiKey = defineSecret("API_KEY");
const dbPassword = defineSecret("DB_PASSWORD");
export const secureEndpoint = onRequest(
{ secrets: [apiKey, dbPassword] },
(req, res) => {
const key = apiKey.value();
const password = dbPassword.value();
res.json({ status: "ok" });
}
);Grant Access to Secret
# Grant function's service account access
gcloud secrets add-iam-policy-binding API_KEY \
--member="serviceAccount:PROJECT_ID@appspot.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"---
Cloud Logging
View Logs
# Firebase CLI
firebase functions:log
firebase functions:log --only myFunction
firebase functions:log -n 100
# gcloud
gcloud logging read "resource.type=cloud_function" --limit=50
gcloud logging read "resource.type=cloud_function AND resource.labels.function_name=myFunction"Structured Logging in Functions
import { logger } from "firebase-functions";
export const myFunction = onRequest((req, res) => {
// Log levels
logger.debug("Debug info");
logger.info("Info message");
logger.warn("Warning");
logger.error("Error occurred");
// Structured data
logger.info("Request received", {
method: req.method,
path: req.path,
userId: req.headers["x-user-id"]
});
res.send("OK");
});Python Logging
import logging
logging.info("Info message")
logging.warning("Warning")
logging.error("Error", extra={"userId": "123"})Log-based Metrics
Create metrics from log entries in GCP Console: 1. Logging > Logs-based Metrics 2. Create Metric 3. Define filter (e.g., severity>=ERROR) 4. Use in Cloud Monitoring dashboards/alerts
---
Cloud Monitoring
View Metrics
GCP Console: Monitoring > Metrics Explorer
Key metrics for Cloud Functions:
cloudfunctions.googleapis.com/function/execution_countcloudfunctions.googleapis.com/function/execution_timescloudfunctions.googleapis.com/function/active_instancescloudfunctions.googleapis.com/function/user_memory_bytes
Firestore metrics:
firestore.googleapis.com/document/read_countfirestore.googleapis.com/document/write_countfirestore.googleapis.com/document/delete_count
Create Alerts
# Create notification channel first
gcloud beta monitoring channels create \
--display-name="Email Alerts" \
--type=email \
--channel-labels=email_address=alerts@example.com
# Create alert policy
gcloud alpha monitoring policies create \
--display-name="High Error Rate" \
--condition-display-name="Error rate > 5%" \
--condition-filter='resource.type="cloud_function" AND metric.type="cloudfunctions.googleapis.com/function/execution_count" AND metric.labels.status!="ok"'Custom Metrics
import { Monitoring } from "@google-cloud/monitoring";
const monitoring = new Monitoring.MetricServiceClient();
const projectId = process.env.GCLOUD_PROJECT;
async function writeCustomMetric(value: number) {
const dataPoint = {
interval: { endTime: { seconds: Date.now() / 1000 } },
value: { doubleValue: value }
};
const timeSeriesData = {
metric: { type: `custom.googleapis.com/my_metric` },
resource: { type: "global", labels: { project_id: projectId } },
points: [dataPoint]
};
await monitoring.createTimeSeries({
name: `projects/${projectId}`,
timeSeries: [timeSeriesData]
});
}---
BigQuery Export
Enable Firebase → BigQuery Export
Firebase Console: 1. Project Settings > Integrations 2. BigQuery > Link 3. Select data to export:
- Analytics
- Crashlytics
- Cloud Messaging
- Performance Monitoring
Query Firebase Data
-- Analytics events
SELECT
event_name,
COUNT(*) as event_count,
COUNT(DISTINCT user_pseudo_id) as unique_users
FROM `project_id.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20240101' AND '20240131'
GROUP BY event_name
ORDER BY event_count DESC;
-- Crashlytics crashes
SELECT
issue_id,
COUNT(*) as crash_count,
ANY_VALUE(issue_title) as title
FROM `project_id.firebase_crashlytics.package_name_ANDROID`
WHERE event_timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
GROUP BY issue_id
ORDER BY crash_count DESC;Export Firestore to BigQuery
Use Firestore Extension or Cloud Function:
import { onDocumentWritten } from "firebase-functions/v2/firestore";
import { BigQuery } from "@google-cloud/bigquery";
const bigquery = new BigQuery();
export const syncToBigQuery = onDocumentWritten(
"orders/{orderId}",
async (event) => {
const data = event.data?.after.data();
if (!data) return;
await bigquery
.dataset("firebase_sync")
.table("orders")
.insert([{
order_id: event.params.orderId,
...data,
synced_at: new Date().toISOString()
}]);
}
);---
Other GCP Services
Cloud Tasks (Delayed Execution)
import { CloudTasksClient } from "@google-cloud/tasks";
const client = new CloudTasksClient();
async function scheduleTask(payload: object, delaySeconds: number) {
const project = process.env.GCLOUD_PROJECT!;
const location = "us-central1";
const queue = "my-queue";
const task = {
httpRequest: {
httpMethod: "POST" as const,
url: `https://${location}-${project}.cloudfunctions.net/processTask`,
body: Buffer.from(JSON.stringify(payload)).toString("base64"),
headers: { "Content-Type": "application/json" }
},
scheduleTime: {
seconds: Math.floor(Date.now() / 1000) + delaySeconds
}
};
await client.createTask({
parent: client.queuePath(project, location, queue),
task
});
}Cloud Pub/Sub
import { PubSub } from "@google-cloud/pubsub";
const pubsub = new PubSub();
// Publish message
async function publishMessage(topicName: string, data: object) {
const topic = pubsub.topic(topicName);
const messageBuffer = Buffer.from(JSON.stringify(data));
await topic.publish(messageBuffer);
}
// Subscribe in Cloud Function
import { onMessagePublished } from "firebase-functions/v2/pubsub";
export const handleMessage = onMessagePublished("my-topic", (event) => {
const data = event.data.message.json;
console.log("Received:", data);
});Cloud Scheduler
Managed by Firebase for scheduled functions. View in GCP Console:
- Cloud Scheduler > Jobs
# List scheduled jobs
gcloud scheduler jobs list
# Manually trigger
gcloud scheduler jobs run my-scheduled-functionCloud Storage (Direct Access)
import { Storage } from "@google-cloud/storage";
const storage = new Storage();
// Upload file
await storage.bucket("my-bucket").upload("local-file.txt", {
destination: "remote-path/file.txt"
});
// Download file
await storage.bucket("my-bucket")
.file("remote-path/file.txt")
.download({ destination: "local-file.txt" });
// Generate signed URL
const [url] = await storage.bucket("my-bucket")
.file("private-file.txt")
.getSignedUrl({
action: "read",
expires: Date.now() + 15 * 60 * 1000 // 15 minutes
});VPC Connector (Private Network)
Connect Cloud Functions to VPC for private resources:
import { onRequest } from "firebase-functions/v2/https";
export const privateNetworkFunction = onRequest(
{
vpcConnector: "my-vpc-connector",
vpcConnectorEgressSettings: "ALL_TRAFFIC"
},
async (req, res) => {
// Can now access private IPs
res.send("Connected to VPC");
}
);# Create VPC connector
gcloud compute networks vpc-access connectors create my-vpc-connector \
--region=us-central1 \
--network=default \
--range=10.8.0.0/28Firebase Hosting Reference
Configuration, rewrites, redirects, headers, and deployment.
Contents
---
Basic Configuration
firebase.json
{
"hosting": {
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"cleanUrls": true,
"trailingSlash": false
}
}Options
| Option | Description | Default |
|---|---|---|
public | Directory to deploy | Required |
ignore | Files to exclude | ["**/.*", "**/node_modules/**"] |
cleanUrls | Remove .html from URLs | false |
trailingSlash | Add trailing slash | false |
appAssociation | iOS/Android app association | - |
i18n | Internationalization config | - |
---
Rewrites
Route requests to different destinations.
SPA (Single Page Application)
{
"hosting": {
"public": "dist",
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}Cloud Functions
{
"hosting": {
"rewrites": [
{
"source": "/api/**",
"function": {
"functionId": "api",
"region": "us-central1"
}
}
]
}
}Shorthand (default region):
{
"source": "/api/**",
"function": "api"
}Cloud Run
{
"hosting": {
"rewrites": [
{
"source": "/app/**",
"run": {
"serviceId": "my-service",
"region": "us-central1"
}
}
]
}
}Dynamic Links (Firebase Hosting)
{
"hosting": {
"rewrites": [
{
"source": "/links/**",
"dynamicLinks": true
}
]
}
}Combined Example
{
"hosting": {
"public": "dist",
"rewrites": [
{
"source": "/api/**",
"function": "api"
},
{
"source": "/admin/**",
"run": {
"serviceId": "admin-dashboard",
"region": "us-central1"
}
},
{
"source": "**",
"destination": "/index.html"
}
]
}
}Order matters: First matching rule wins.
---
Redirects
Basic Redirects
{
"hosting": {
"redirects": [
{
"source": "/old-page",
"destination": "/new-page",
"type": 301
},
{
"source": "/legacy/**",
"destination": "https://legacy.example.com/:splat",
"type": 302
}
]
}
}Redirect Types
| Type | Description |
|---|---|
301 | Permanent redirect (cached by browsers) |
302 | Temporary redirect |
With Path Segments
{
"redirects": [
{
"source": "/blog/:slug",
"destination": "/articles/:slug",
"type": 301
},
{
"source": "/users/:uid/profile",
"destination": "/profiles/:uid",
"type": 301
}
]
}Glob Patterns
{
"redirects": [
{
"source": "/old/**",
"destination": "/new/:splat",
"type": 301
},
{
"source": "/docs/v1/**",
"destination": "/docs/latest/:splat",
"type": 302
}
]
}External Redirects
{
"redirects": [
{
"source": "/github",
"destination": "https://github.com/myorg",
"type": 302
}
]
}---
Headers
Cache Control
{
"hosting": {
"headers": [
{
"source": "**/*.@(jpg|jpeg|gif|png|svg|webp)",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=31536000, immutable"
}
]
},
{
"source": "**/*.@(js|css)",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=31536000"
}
]
},
{
"source": "**/*.html",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache"
}
]
}
]
}
}Security Headers
{
"hosting": {
"headers": [
{
"source": "**",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
},
{
"key": "Referrer-Policy",
"value": "strict-origin-when-cross-origin"
},
{
"key": "Permissions-Policy",
"value": "camera=(), microphone=(), geolocation=()"
}
]
}
]
}
}CORS Headers
{
"hosting": {
"headers": [
{
"source": "/api/**",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "*"
},
{
"key": "Access-Control-Allow-Methods",
"value": "GET, POST, OPTIONS"
},
{
"key": "Access-Control-Allow-Headers",
"value": "Content-Type, Authorization"
}
]
}
]
}
}Content Security Policy
{
"headers": [
{
"source": "**",
"headers": [
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; script-src 'self' 'unsafe-inline' https://apis.google.com; style-src 'self' 'unsafe-inline'"
}
]
}
]
}---
Preview Channels
Deploy to temporary URLs for testing.
Create and Deploy
# Deploy to preview channel
firebase hosting:channel:deploy preview-feature-x
# With expiration
firebase hosting:channel:deploy preview --expires 7d
firebase hosting:channel:deploy preview --expires 30d
# List channels
firebase hosting:channel:list
# Delete channel
firebase hosting:channel:delete preview-feature-xExpiration Options
1h,2h, ... (hours)1d,7d,30d(days)- Max: 30 days
Promote to Live
# Clone preview to live
firebase hosting:clone project-id:preview-channel project-id:liveCI/CD Integration
# GitHub Actions example
- name: Deploy to preview
run: |
firebase hosting:channel:deploy pr-${{ github.event.pull_request.number }} \
--expires 7d \
--token ${{ secrets.FIREBASE_TOKEN }}---
Multi-site Hosting
Host multiple sites from one project.
Create Additional Sites
firebase hosting:sites:create my-admin-site
firebase hosting:sites:listConfigure firebase.json
{
"hosting": [
{
"target": "main",
"public": "dist/main",
"rewrites": [
{ "source": "**", "destination": "/index.html" }
]
},
{
"target": "admin",
"public": "dist/admin",
"rewrites": [
{ "source": "**", "destination": "/index.html" }
]
}
]
}Apply Deploy Targets
firebase target:apply hosting main my-project
firebase target:apply hosting admin my-admin-siteThis creates entries in .firebaserc:
{
"projects": {
"default": "my-project"
},
"targets": {
"my-project": {
"hosting": {
"main": ["my-project"],
"admin": ["my-admin-site"]
}
}
}
}Deploy Specific Sites
firebase deploy --only hosting:main
firebase deploy --only hosting:admin
firebase deploy --only hosting # All sites---
Deployment
Deploy Commands
# Deploy hosting only
firebase deploy --only hosting
# With message
firebase deploy --only hosting --message "Version 1.2.0"
# Specific site (multi-site)
firebase deploy --only hosting:mainRollback
# Rollback to previous version
firebase hosting:rollbackView Deploys
# In console
firebase open hosting---
Complete Example
{
"hosting": {
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"cleanUrls": true,
"trailingSlash": false,
"redirects": [
{
"source": "/old/**",
"destination": "/new/:splat",
"type": 301
}
],
"rewrites": [
{
"source": "/api/**",
"function": {
"functionId": "api",
"region": "us-central1"
}
},
{
"source": "**",
"destination": "/index.html"
}
],
"headers": [
{
"source": "**/*.@(jpg|jpeg|gif|png|svg|webp|js|css)",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=31536000"
}
]
},
{
"source": "**",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
}
]
}
]
}
}#!/bin/bash
# Deploy Cloud Functions with options for specific functions
# Usage: ./deploy_functions.sh [options] [function-names...]
# --dry-run Preview changes without deploying
# --force Skip confirmation prompt
# --codebase <cb> Deploy specific codebase only
# <function-names> Space-separated list of specific functions to deploy
set -e
DRY_RUN=""
FORCE=false
CODEBASE=""
FUNCTIONS=()
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run)
DRY_RUN="--dry-run"
shift
;;
--force|-f)
FORCE=true
shift
;;
--codebase)
CODEBASE="$2"
shift 2
;;
-*)
echo "Unknown option: $1"
exit 1
;;
*)
FUNCTIONS+=("$1")
shift
;;
esac
done
# Get current project
PROJECT=$(firebase use 2>/dev/null | grep -oP '(?<=Active Project: ).*' || echo "unknown")
echo "🔥 Firebase Functions Deployment"
echo "================================="
echo "Project: $PROJECT"
echo ""
# Build target string
if [ ${#FUNCTIONS[@]} -gt 0 ]; then
# Specific functions
FUNC_LIST=$(IFS=,; echo "${FUNCTIONS[*]}")
TARGET="functions:$FUNC_LIST"
echo "Functions: ${FUNCTIONS[*]}"
else
# All functions
TARGET="functions"
echo "Functions: all"
fi
if [ -n "$CODEBASE" ]; then
echo "Codebase: $CODEBASE"
fi
if [ -n "$DRY_RUN" ]; then
echo "Mode: DRY RUN"
fi
echo ""
# Confirmation
if [ "$FORCE" = false ] && [ -z "$DRY_RUN" ]; then
read -p "Deploy functions to $PROJECT? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Deployment cancelled."
exit 0
fi
fi
# Build command
CMD="firebase deploy --only $TARGET $DRY_RUN"
[ -n "$CODEBASE" ] && CMD="$CMD --codebase $CODEBASE"
echo "Running: $CMD"
echo ""
eval $CMD
echo ""
echo "✅ Functions deployment complete!"
echo ""
echo "📋 Useful commands:"
echo " • View logs: firebase functions:log"
echo " • Specific logs: firebase functions:log --only <functionName>"
echo " • Delete function: firebase functions:delete <functionName>"
echo " • List secrets: firebase functions:secrets:list"
#!/bin/bash
# Deploy Firebase project with confirmation and options
# Usage: ./deploy.sh [options]
# --dry-run Preview changes without deploying
# --only <target> Deploy specific targets (hosting, functions, firestore, storage)
# --except <target> Deploy all except specified targets
# --force Skip confirmation prompt
# --message <msg> Add deployment message (for hosting)
set -e
DRY_RUN=""
ONLY_FLAG=""
EXCEPT_FLAG=""
FORCE=false
MESSAGE=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run)
DRY_RUN="--dry-run"
shift
;;
--only)
ONLY_FLAG="--only $2"
shift 2
;;
--except)
EXCEPT_FLAG="--except $2"
shift 2
;;
--force|-f)
FORCE=true
shift
;;
--message|-m)
MESSAGE="--message \"$2\""
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# Get current project
PROJECT=$(firebase use 2>/dev/null | grep -oP '(?<=Active Project: ).*' || echo "unknown")
echo "🔥 Firebase Deployment"
echo "======================"
echo "Project: $PROJECT"
echo ""
# Show what will be deployed
if [ -n "$ONLY_FLAG" ]; then
echo "Targets: ${ONLY_FLAG#--only }"
elif [ -n "$EXCEPT_FLAG" ]; then
echo "Targets: all except ${EXCEPT_FLAG#--except }"
else
echo "Targets: all (hosting, functions, firestore rules, storage rules)"
fi
if [ -n "$DRY_RUN" ]; then
echo "Mode: DRY RUN (no changes will be made)"
fi
echo ""
# Confirmation
if [ "$FORCE" = false ] && [ -z "$DRY_RUN" ]; then
read -p "Deploy to $PROJECT? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Deployment cancelled."
exit 0
fi
fi
# Build and execute command
CMD="firebase deploy $ONLY_FLAG $EXCEPT_FLAG $DRY_RUN $MESSAGE"
echo "Running: $CMD"
echo ""
eval $CMD
echo ""
echo "✅ Deployment complete!"
echo ""
echo "📋 Useful commands:"
echo " • View logs: firebase functions:log"
echo " • Rollback: firebase hosting:rollback (hosting only)"
echo " • Open console: firebase open"
#!/bin/bash
# Export Firestore data to a local directory or GCS bucket
# Usage: ./export_firestore.sh [options]
# --output <path> Local directory or gs:// bucket path (default: ./firestore-export)
# --collections <list> Comma-separated list of collections (default: all)
# --emulator Export from local emulator instead of production
set -e
OUTPUT_PATH="./firestore-export"
COLLECTIONS=""
FROM_EMULATOR=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--output|-o)
OUTPUT_PATH="$2"
shift 2
;;
--collections|-c)
COLLECTIONS="$2"
shift 2
;;
--emulator)
FROM_EMULATOR=true
shift
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
echo "🔥 Firestore Export"
echo "==================="
if [ "$FROM_EMULATOR" = true ]; then
# Export from emulator
echo "Source: Local Emulator"
echo "Output: $OUTPUT_PATH"
echo ""
# Check if emulator is running
if ! curl -s http://localhost:8080/ > /dev/null 2>&1; then
echo "❌ Firestore emulator not running on port 8080"
echo " Start emulators first: firebase emulators:start"
exit 1
fi
echo "Exporting emulator data..."
curl -X POST "http://localhost:8080/emulator/v1/projects/$(firebase use)/databases/(default)/documents:exportDocuments" \
-H "Content-Type: application/json" \
-d "{\"outputUriPrefix\": \"$OUTPUT_PATH\"}" || {
# Fallback: Use emulator export via firebase CLI
echo "Using firebase emulators:export..."
firebase emulators:export "$OUTPUT_PATH"
}
else
# Export from production
PROJECT=$(firebase use 2>/dev/null | grep -oP '(?<=Active Project: ).*' || echo "")
if [ -z "$PROJECT" ]; then
echo "❌ No active project. Run: firebase use <project-id>"
exit 1
fi
echo "Source: $PROJECT (PRODUCTION)"
echo "Output: $OUTPUT_PATH"
echo ""
# Warning for production export
echo "⚠️ This exports from PRODUCTION Firestore"
read -p "Continue? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Export cancelled."
exit 0
fi
# Build gcloud command
if [[ "$OUTPUT_PATH" == gs://* ]]; then
BUCKET="$OUTPUT_PATH"
else
echo "❌ Production export requires a GCS bucket (gs://bucket-name/path)"
echo " For local exports, use --emulator flag"
exit 1
fi
CMD="gcloud firestore export $BUCKET --project=$PROJECT"
if [ -n "$COLLECTIONS" ]; then
# Convert comma-separated to space-separated for gcloud
COLL_FLAGS=""
IFS=',' read -ra COLL_ARRAY <<< "$COLLECTIONS"
for coll in "${COLL_ARRAY[@]}"; do
COLL_FLAGS="$COLL_FLAGS --collection-ids=$coll"
done
CMD="$CMD $COLL_FLAGS"
fi
echo "Running: $CMD"
eval $CMD
fi
echo ""
echo "✅ Export complete!"
echo " Location: $OUTPUT_PATH"
#!/bin/bash
# Import Firestore data from a local directory or GCS bucket
# Usage: ./import_firestore.sh [options]
# --input <path> Local directory or gs:// bucket path (required)
# --collections <list> Comma-separated list of collections (default: all)
# --emulator Import to local emulator instead of production
set -e
INPUT_PATH=""
COLLECTIONS=""
TO_EMULATOR=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--input|-i)
INPUT_PATH="$2"
shift 2
;;
--collections|-c)
COLLECTIONS="$2"
shift 2
;;
--emulator)
TO_EMULATOR=true
shift
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
if [ -z "$INPUT_PATH" ]; then
echo "❌ --input <path> is required"
echo "Usage: ./import_firestore.sh --input <path> [--emulator] [--collections <list>]"
exit 1
fi
echo "🔥 Firestore Import"
echo "==================="
if [ "$TO_EMULATOR" = true ]; then
# Import to emulator
echo "Target: Local Emulator"
echo "Source: $INPUT_PATH"
echo ""
# Check if emulator is running
if ! curl -s http://localhost:8080/ > /dev/null 2>&1; then
echo "❌ Firestore emulator not running on port 8080"
echo " Start emulators first: firebase emulators:start"
exit 1
fi
# For emulator, we use the emulators:start --import flag
# This script provides guidance for already-running emulators
echo "To import data into the emulator:"
echo ""
echo "Option 1: Start emulators with import flag"
echo " firebase emulators:start --import=$INPUT_PATH"
echo ""
echo "Option 2: Use REST API (if emulator supports it)"
echo " curl -X POST 'http://localhost:8080/emulator/v1/projects/PROJECT_ID/databases/(default)/documents:importDocuments'"
echo ""
echo "💡 Tip: The easiest approach is to stop emulators and restart with --import"
else
# Import to production
PROJECT=$(firebase use 2>/dev/null | grep -oP '(?<=Active Project: ).*' || echo "")
if [ -z "$PROJECT" ]; then
echo "❌ No active project. Run: firebase use <project-id>"
exit 1
fi
echo "Target: $PROJECT (PRODUCTION)"
echo "Source: $INPUT_PATH"
echo ""
# Strong warning for production import
echo "⚠️ WARNING: This imports data to PRODUCTION Firestore"
echo " This operation can overwrite existing documents!"
echo ""
read -p "Type the project ID to confirm: " CONFIRM_PROJECT
if [ "$CONFIRM_PROJECT" != "$PROJECT" ]; then
echo "❌ Project ID doesn't match. Import cancelled."
exit 1
fi
# Validate GCS path for production
if [[ "$INPUT_PATH" != gs://* ]]; then
echo "❌ Production import requires a GCS bucket path (gs://bucket-name/path)"
exit 1
fi
CMD="gcloud firestore import $INPUT_PATH --project=$PROJECT"
if [ -n "$COLLECTIONS" ]; then
IFS=',' read -ra COLL_ARRAY <<< "$COLLECTIONS"
for coll in "${COLL_ARRAY[@]}"; do
CMD="$CMD --collection-ids=$coll"
done
fi
echo "Running: $CMD"
eval $CMD
echo ""
echo "✅ Import initiated!"
echo " Monitor progress in Cloud Console > Firestore > Import/Export"
fi
#!/bin/bash
# Initialize a Firebase project with functions, Firestore, hosting, and emulators
# Usage: ./init_project.sh [project-id]
# If project-id is omitted, prompts for selection
set -e
PROJECT_ID="${1:-}"
FEATURES="firestore,functions,hosting,storage,emulators"
echo "🔥 Firebase Project Initialization"
echo "==================================="
# Check Firebase CLI is installed
if ! command -v firebase &> /dev/null; then
echo "❌ Firebase CLI not found. Install with: npm install -g firebase-tools"
exit 1
fi
# Check authentication
if ! firebase projects:list &> /dev/null; then
echo "❌ Not authenticated. Run: firebase login"
exit 1
fi
# Initialize with project
if [ -n "$PROJECT_ID" ]; then
echo "📁 Initializing with project: $PROJECT_ID"
firebase init $FEATURES --project "$PROJECT_ID"
else
echo "📁 Initializing Firebase (will prompt for project selection)"
firebase init $FEATURES
fi
# Post-init recommendations
echo ""
echo "✅ Firebase initialized successfully!"
echo ""
echo "📋 Next steps:"
echo " 1. Review firebase.json configuration"
echo " 2. Set up firestore.rules and storage.rules"
echo " 3. Configure functions/ directory"
echo " 4. Start emulators: firebase emulators:start"
echo ""
echo "📚 Emulator ports (defaults):"
echo " • Auth: http://localhost:9099"
echo " • Functions: http://localhost:5001"
echo " • Firestore: http://localhost:8080"
echo " • Storage: http://localhost:9199"
echo " • Hosting: http://localhost:5000"
echo " • UI: http://localhost:4000"