To debug Firebase Cloud Functions locally, use the Firebase Emulator Suite. Run firebase emulators:start to launch a local functions emulator, then connect your app to it using connectFunctionsEmulator. You can set breakpoints in VS Code by attaching the Node.js debugger to the emulator process, inspect logs in the Emulator UI at localhost:4000, and test both HTTP and callable functions without deploying to production.
Debugging Firebase Functions Locally with the Emulator Suite
Deploying Cloud Functions to test every small change is slow and expensive. The Firebase Emulator Suite lets you run functions on your local machine with full logging, breakpoint debugging, and hot-reload support. This tutorial walks you through setting up the emulator, connecting your frontend, attaching a debugger, and reading logs to quickly find and fix issues.
Prerequisites
- Node.js 18 or later installed
- Firebase CLI installed globally (npm install -g firebase-tools)
- A Firebase project initialized with Cloud Functions (firebase init functions)
- VS Code or another editor with Node.js debugging support
Step-by-step guide
Install and initialize the Firebase Emulator Suite
Install and initialize the Firebase Emulator Suite
If you have not set up emulators yet, run firebase init emulators in your project directory. Select the Functions emulator (and optionally Firestore, Auth, and Storage if your functions interact with those services). The CLI will download the required emulator binaries. Accept the default ports or customize them. The functions emulator defaults to port 5001.
1# In your Firebase project root2firebase init emulators34# Select: Functions Emulator, Firestore Emulator (optional)5# Accept default ports or customize6# Download emulators when promptedExpected result: Your firebase.json now contains an emulators section with port assignments for each selected service.
Start the emulators
Start the emulators
Run firebase emulators:start from your project root. The CLI compiles your functions and starts each emulator on its configured port. The Emulator UI launches at localhost:4000, giving you a dashboard to browse Firestore data, manage Auth users, and view function logs. If your functions use environment variables, create a .env file in your functions directory — the emulator reads it automatically.
1# Start all configured emulators2firebase emulators:start34# To persist data between sessions:5firebase emulators:start --export-on-exit=./emulator-data --import=./emulator-dataExpected result: Terminal output shows each emulator starting with its port. The Emulator UI is accessible at http://localhost:4000.
Connect your client app to the local functions emulator
Connect your client app to the local functions emulator
In your frontend code, use connectFunctionsEmulator to redirect all function calls to localhost instead of production. This should only run in development mode. The emulator handles both onCall and onRequest functions identically to production, including authentication context when the Auth emulator is also running.
1import { initializeApp } from 'firebase/app';2import { getFunctions, connectFunctionsEmulator } from 'firebase/functions';34const app = initializeApp(firebaseConfig);5const functions = getFunctions(app);67// Connect to local emulator in development8if (process.env.NODE_ENV === 'development') {9 connectFunctionsEmulator(functions, '127.0.0.1', 5001);10}Expected result: Client-side function calls now hit localhost:5001 instead of the production Cloud Functions endpoint.
Write a test function and trigger it locally
Write a test function and trigger it locally
Create a simple callable function to verify the emulator is working. Export it from your functions/index.ts file. After saving, the emulator hot-reloads automatically. Call the function from your app or use curl for an onRequest function. Watch the Emulator UI Logs tab for the invocation details.
1// functions/src/index.ts2import { onCall, HttpsError } from 'firebase-functions/v2/https';3import { logger } from 'firebase-functions/v2';45export const greet = onCall((request) => {6 logger.info('greet called', { uid: request.auth?.uid });78 if (!request.auth) {9 throw new HttpsError('unauthenticated', 'Must be signed in');10 }1112 const name = request.data.name || 'World';13 logger.info('Greeting', { name });14 return { message: `Hello, ${name}!` };15});Expected result: Calling the greet function from your app returns a greeting message, and the Emulator UI shows the log entries.
Attach VS Code debugger for breakpoint debugging
Attach VS Code debugger for breakpoint debugging
To set breakpoints and step through function code, start the emulator with the --inspect-functions flag. This opens a Node.js debugging port (default 9229). Then create a VS Code launch configuration that attaches to that port. Set breakpoints in your function code, trigger the function, and VS Code will pause execution at each breakpoint.
1// Start emulator with inspect flag2// firebase emulators:start --inspect-functions34// .vscode/launch.json5{6 "version": "0.2.0",7 "configurations": [8 {9 "type": "node",10 "request": "attach",11 "name": "Attach to Functions Emulator",12 "port": 9229,13 "restart": true,14 "skipFiles": ["<node_internals>/**"]15 }16 ]17}Expected result: VS Code connects to the emulator process. Breakpoints in function code pause execution and show local variables, call stack, and request data.
Read and filter logs in the Emulator UI
Read and filter logs in the Emulator UI
Open localhost:4000 in your browser and navigate to the Logs tab. All logger.info, logger.warn, and logger.error calls from your functions appear here in real time. You can filter by severity level and search for specific text. Structured log data (objects passed as second argument to logger methods) is displayed as expandable JSON, making it easy to inspect request payloads and error details.
1// Use structured logging for better debugging2import { logger } from 'firebase-functions/v2';34// Instead of:5logger.info('User created: ' + uid);67// Use structured data:8logger.info('User created', {9 uid,10 email: request.data.email,11 timestamp: new Date().toISOString()12});Expected result: The Emulator UI Logs tab shows real-time log entries with expandable structured data for each function invocation.
Complete working example
1import { onCall, onRequest, HttpsError } from 'firebase-functions/v2/https';2import { logger } from 'firebase-functions/v2';3import { initializeApp } from 'firebase-admin/app';4import { getFirestore } from 'firebase-admin/firestore';56initializeApp();7const db = getFirestore();89// Callable function — auto-handles auth and CORS10export const greet = onCall((request) => {11 logger.info('greet called', { uid: request.auth?.uid });1213 if (!request.auth) {14 throw new HttpsError('unauthenticated', 'Must be signed in');15 }1617 const name = request.data.name || 'World';18 return { message: `Hello, ${name}!` };19});2021// HTTP function — test with curl http://localhost:5001/PROJECT/REGION/healthCheck22export const healthCheck = onRequest((req, res) => {23 logger.info('Health check hit', { method: req.method });24 res.json({ status: 'ok', timestamp: new Date().toISOString() });25});2627// Function that reads Firestore — tests emulator integration28export const getUserProfile = onCall(async (request) => {29 if (!request.auth) {30 throw new HttpsError('unauthenticated', 'Must be signed in');31 }3233 const uid = request.auth.uid;34 logger.info('Fetching profile', { uid });3536 const doc = await db.collection('users').doc(uid).get();3738 if (!doc.exists) {39 logger.warn('Profile not found', { uid });40 throw new HttpsError('not-found', 'User profile does not exist');41 }4243 logger.info('Profile found', { uid, data: doc.data() });44 return doc.data();45});Common mistakes when debugging Firebase Functions Locally with the Emulator Suite
Why it's a problem: Forgetting to call connectFunctionsEmulator and accidentally hitting production functions during development
How to avoid: Always wrap emulator connection calls in an environment check (process.env.NODE_ENV === 'development') and add it early in your app initialization before any function calls are made.
Why it's a problem: Functions not reloading after code changes in TypeScript projects
How to avoid: The emulator watches compiled JavaScript, not TypeScript source. Run tsc --watch in a separate terminal so TypeScript recompiles automatically on every save.
Why it's a problem: Port conflicts when starting the emulator because another process is using port 5001
How to avoid: Change the functions emulator port in firebase.json under emulators.functions.port, then update your connectFunctionsEmulator call to match the new port.
Why it's a problem: Environment variables and secrets not available in the emulator
How to avoid: Create a .env file in your functions directory with your variables. The emulator reads .env and .env.local automatically. For defineSecret values, set them as regular environment variables in .env during local development.
Best practices
- Always use the --inspect-functions flag when you need to step through code with breakpoints
- Use structured logging (logger.info with object payloads) instead of string concatenation for easier debugging
- Run tsc --watch alongside the emulator for automatic TypeScript recompilation
- Use --export-on-exit and --import flags to persist emulator data between sessions
- Guard connectFunctionsEmulator calls with environment checks to avoid connecting to localhost in production
- Start all dependent emulators together (Functions + Firestore + Auth) to test the full integration locally
- Test error paths explicitly by calling functions without auth or with invalid data to verify HttpsError handling
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Show me how to set up the Firebase Emulator Suite to debug Cloud Functions locally. Include the firebase.json emulator config, how to start the emulator with inspect mode, a VS Code launch.json for attaching the debugger, and how to connect a React frontend to the local functions emulator.
Set up Firebase Emulator Suite for local function debugging. Create a callable function with structured logging, configure the emulators in firebase.json, add a VS Code launch.json for breakpoint debugging with --inspect-functions, and show how to connect the client to the local emulator with connectFunctionsEmulator.
Frequently asked questions
Does the Firebase Functions emulator require a Blaze plan?
No. The emulator runs entirely on your local machine and does not consume any Firebase resources or require billing. You can develop and test functions on the free Spark plan.
Can I debug Firestore triggers in the emulator?
Yes. When you run both the Functions and Firestore emulators together, writing data to the Firestore emulator triggers onDocumentCreated, onDocumentUpdated, and other Firestore-triggered functions just like in production.
Why does my function return 'Function returned undefined' in the emulator?
This means your function is not returning a value or a Promise. For async functions, make sure you return the result of await calls. For callback-based code, return a Promise that resolves when the work is complete.
How do I test onRequest functions locally without a frontend?
Use curl or any HTTP client. The emulator logs the URL for each function at startup, typically http://localhost:5001/YOUR_PROJECT/us-central1/functionName. Send GET or POST requests directly to that URL.
Does the emulator support v2 (2nd gen) Cloud Functions?
Yes. The Firebase Emulator Suite fully supports both v1 and v2 Cloud Functions, including onCall, onRequest, Firestore triggers, Auth triggers, and scheduled functions.
Can I use the emulator in CI/CD pipelines for automated testing?
Yes. Run firebase emulators:exec 'npm test' to start emulators, run your test suite, and shut down automatically. This is the recommended approach for automated testing in CI environments.
Can RapidDev help set up a local Firebase development workflow?
Yes, RapidDev can configure the full Emulator Suite, set up VS Code debugging, write integration tests, and establish CI/CD pipelines that test functions locally before deploying to production.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation