What are Cloud Functions?
Cloud Functions for Firebase let you run backend code automatically in response to events triggered by Firebase features and HTTPS requests. Your code is stored in Google's cloud and runs in a managed environment, so there's no need to manage or scale your own servers.
Functions can be triggered by events from Firebase services (Authentication, Firestore, Storage, etc.), HTTP requests, scheduled times (cron), or Google Cloud services.
β‘ Common Use Cases
- π Notifications: Send push notifications when data changes.
- π§ Emails: Send welcome emails when users sign up.
- πΌοΈ Image Processing: Generate thumbnails when images are uploaded.
- π Data Validation: Validate and sanitize data before storage.
- π APIs: Build REST APIs and webhooks.
- β° Scheduled Tasks: Run cleanup jobs on a schedule.
Setting Up Cloud Functions
Initialize Cloud Functions in your project:
# Initialize functions in your project
firebase init functions
# Choose options:
# - JavaScript or TypeScript
# - ESLint (recommended)
# - Install dependencies
This creates a functions directory structure:
my-project/
βββ functions/
β βββ src/
β β βββ index.ts # Function entry point
β βββ package.json
β βββ tsconfig.json
βββ firebase.json
βββ ...
HTTP Functions
Create functions that respond to HTTP requests:
import { onRequest } from 'firebase-functions/v2/https';
// Simple HTTP function
export const helloWorld = onRequest((request, response) => {
response.send('Hello from Firebase!');
});
// With CORS enabled
export const api = onRequest({ cors: true }, (request, response) => {
const { name } = request.query;
response.json({ message: `Hello, ${name || 'World'}!` });
});
// Handle different HTTP methods
export const users = onRequest({ cors: true }, async (request, response) => {
switch (request.method) {
case 'GET':
// Handle GET request
response.json({ users: [] });
break;
case 'POST':
// Handle POST request
const { email, name } = request.body;
response.json({ id: 'new-user-id', email, name });
break;
default:
response.status(405).send('Method Not Allowed');
}
});
Firestore Triggers
Run functions when Firestore documents change:
import { onDocumentCreated, onDocumentUpdated, onDocumentDeleted } from 'firebase-functions/v2/firestore';
import { getFirestore } from 'firebase-admin/firestore';
// Initialize admin SDK
import { initializeApp } from 'firebase-admin/app';
initializeApp();
const db = getFirestore();
// Trigger when a document is created
export const onUserCreated = onDocumentCreated('users/{userId}', async (event) => {
const snapshot = event.data;
if (!snapshot) return;
const userData = snapshot.data();
const userId = event.params.userId;
// Create a profile document
await db.collection('profiles').doc(userId).set({
displayName: userData.name,
createdAt: new Date(),
postsCount: 0
});
console.log(`Profile created for user: ${userId}`);
});
// Trigger when a document is updated
export const onUserUpdated = onDocumentUpdated('users/{userId}', async (event) => {
const before = event.data?.before.data();
const after = event.data?.after.data();
// Check what changed
if (before?.email !== after?.email) {
console.log(`Email changed from ${before?.email} to ${after?.email}`);
// Send email verification
}
});
// Trigger when a document is deleted
export const onUserDeleted = onDocumentDeleted('users/{userId}', async (event) => {
const userId = event.params.userId;
// Clean up related data
await db.collection('profiles').doc(userId).delete();
await db.collection('posts').where('authorId', '==', userId).get()
.then(snapshot => {
const batch = db.batch();
snapshot.docs.forEach(doc => batch.delete(doc.ref));
return batch.commit();
});
});
Authentication Triggers
Run functions when users sign up or delete their account:
import { beforeUserCreated, beforeUserSignedIn } from 'firebase-functions/v2/identity';
import { onCall } from 'firebase-functions/v2/https';
import { getAuth } from 'firebase-admin/auth';
// Before user is created (can modify or block)
export const validateNewUser = beforeUserCreated((event) => {
const user = event.data;
// Block sign-ups from certain domains
if (user.email?.endsWith('@blocked.com')) {
throw new Error('This email domain is not allowed');
}
// Add custom claims
return {
customClaims: {
role: 'user',
createdVia: 'signup'
}
};
});
// Callable function to set admin role
export const setAdminRole = onCall(async (request) => {
// Check if caller is admin
if (request.auth?.token.role !== 'admin') {
throw new Error('Only admins can set admin roles');
}
const { userId } = request.data;
await getAuth().setCustomUserClaims(userId, { role: 'admin' });
return { success: true };
});
Storage Triggers
Process files when they're uploaded to Storage:
import { onObjectFinalized, onObjectDeleted } from 'firebase-functions/v2/storage';
import { getStorage } from 'firebase-admin/storage';
import * as path from 'path';
// Trigger when a file is uploaded
export const processImage = onObjectFinalized(async (event) => {
const filePath = event.data.name;
const contentType = event.data.contentType;
// Only process images
if (!contentType?.startsWith('image/')) {
console.log('Not an image, skipping');
return;
}
// Skip if already a thumbnail
if (filePath?.includes('thumb_')) {
return;
}
const bucket = getStorage().bucket(event.data.bucket);
const fileName = path.basename(filePath || '');
const fileDir = path.dirname(filePath || '');
// Download, resize, and upload thumbnail
const tempFilePath = `/tmp/${fileName}`;
await bucket.file(filePath!).download({ destination: tempFilePath });
// Use sharp or another library for image processing
// const sharp = require('sharp');
// await sharp(tempFilePath).resize(200, 200).toFile(thumbPath);
const thumbFileName = `thumb_${fileName}`;
const thumbFilePath = path.join(fileDir, thumbFileName);
await bucket.upload(tempFilePath, {
destination: thumbFilePath,
metadata: { contentType }
});
console.log(`Thumbnail created: ${thumbFilePath}`);
});
// Trigger when a file is deleted
export const onFileDeleted = onObjectDeleted(async (event) => {
const filePath = event.data.name;
console.log(`File deleted: ${filePath}`);
// Delete associated thumbnail if exists
if (!filePath?.includes('thumb_')) {
const bucket = getStorage().bucket(event.data.bucket);
const fileName = path.basename(filePath || '');
const fileDir = path.dirname(filePath || '');
const thumbPath = path.join(fileDir, `thumb_${fileName}`);
try {
await bucket.file(thumbPath).delete();
} catch (error) {
// Thumbnail might not exist
}
}
});
Scheduled Functions
Run functions on a schedule:
import { onSchedule } from 'firebase-functions/v2/scheduler';
import { getFirestore } from 'firebase-admin/firestore';
// Run every day at midnight
export const dailyCleanup = onSchedule('0 0 * * *', async (event) => {
const db = getFirestore();
// Delete documents older than 30 days
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const oldDocs = await db.collection('logs')
.where('createdAt', '<', thirtyDaysAgo)
.get();
const batch = db.batch();
oldDocs.docs.forEach(doc => batch.delete(doc.ref));
await batch.commit();
console.log(`Deleted ${oldDocs.size} old documents`);
});
// Run every hour
export const hourlySync = onSchedule('0 * * * *', async (event) => {
console.log('Running hourly sync...');
// Sync data with external service
});
// Run every Monday at 9 AM
export const weeklyReport = onSchedule('0 9 * * 1', async (event) => {
console.log('Generating weekly report...');
// Generate and send report
});
Callable Functions
Functions that can be called directly from client code:
// functions/src/index.ts
import { onCall, HttpsError } from 'firebase-functions/v2/https';
export const addNumbers = onCall((request) => {
const { a, b } = request.data;
// Validate input
if (typeof a !== 'number' || typeof b !== 'number') {
throw new HttpsError('invalid-argument', 'Both arguments must be numbers');
}
return { result: a + b };
});
// Protected callable function
export const getUserData = onCall(async (request) => {
// Check if user is authenticated
if (!request.auth) {
throw new HttpsError('unauthenticated', 'User must be logged in');
}
const userId = request.auth.uid;
const db = getFirestore();
const userDoc = await db.collection('users').doc(userId).get();
if (!userDoc.exists) {
throw new HttpsError('not-found', 'User not found');
}
return userDoc.data();
});
// Client-side usage
import { getFunctions, httpsCallable } from 'firebase/functions';
const functions = getFunctions();
// Call the function
const addNumbers = httpsCallable(functions, 'addNumbers');
const result = await addNumbers({ a: 5, b: 3 });
console.log(result.data.result); // 8
// Call protected function
const getUserData = httpsCallable(functions, 'getUserData');
const userData = await getUserData();
console.log(userData.data);
Environment Configuration
Manage secrets and configuration:
import { defineSecret, defineString } from 'firebase-functions/params';
// Define secrets (stored securely)
const apiKey = defineSecret('API_KEY');
const dbPassword = defineSecret('DB_PASSWORD');
// Define config values
const welcomeMessage = defineString('WELCOME_MESSAGE', {
default: 'Welcome to our app!'
});
// Use in function
export const secureFunction = onRequest(
{ secrets: [apiKey, dbPassword] },
async (request, response) => {
// Access secret values
const key = apiKey.value();
const password = dbPassword.value();
// Use the secrets...
response.json({ message: welcomeMessage.value() });
}
);
// Set secrets via CLI:
// firebase functions:secrets:set API_KEY
// firebase functions:secrets:set DB_PASSWORD
Deploying Functions
Deploy your functions to Firebase:
# Deploy all functions
firebase deploy --only functions
# Deploy specific function
firebase deploy --only functions:helloWorld
# Deploy multiple specific functions
firebase deploy --only functions:helloWorld,functions:api
# View function logs
firebase functions:log
# View logs for specific function
firebase functions:log --only helloWorld
Local Testing with Emulator
Test functions locally before deploying:
# Start the emulator
firebase emulators:start --only functions
# Or start all emulators
firebase emulators:start
# Functions will be available at:
# http://localhost:5001/YOUR_PROJECT/us-central1/functionName
# Call HTTP functions
curl http://localhost:5001/my-project/us-central1/helloWorld
# View emulator UI
# http://localhost:4000
π‘ Key Takeaways
- β’ Cloud Functions run serverless backend code in response to events
- β’ HTTP functions create REST APIs and webhooks
- β’ Firestore/Auth/Storage triggers respond to data changes
- β’ Scheduled functions run on cron schedules
- β’ Callable functions are invoked directly from client code
- β’ Use the emulator for local testing before deployment
π Learn More
-
Cloud Functions Documentation β
Official Firebase Cloud Functions documentation with guides and API reference.
-
Get Started with Cloud Functions β
Step-by-step guide to writing and deploying your first function.
-
Callable Functions Guide β
Learn how to call functions directly from your client apps.
-
Firestore Triggers β
Respond to Firestore document changes with Cloud Functions.
-
Functions Emulator Guide β
Test functions locally before deploying to production.