Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Use Firebase Cloud Functions with FlutterFlow

Firebase Cloud Functions let you run server-side Node.js code triggered by HTTP calls, Firestore events, or schedules — all without a dedicated server. In FlutterFlow, you write the function code outside the visual editor (in the Firebase Functions directory), deploy via CLI, then call it from a FlutterFlow Custom Action using the firebase_functions package. Never write Cloud Function code in FlutterFlow's Custom Code panel — that panel is for client-side Dart only.

What you'll learn

  • How Cloud Functions fit into a FlutterFlow app architecture and when to use each function type
  • How to write, configure, and deploy onCall, onRequest, Firestore trigger, and scheduled functions
  • How to call Cloud Functions securely from FlutterFlow Custom Actions
  • How to manage environment variables and secrets in Cloud Functions
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate11 min read45-60 minFlutterFlow Pro+ (code export required for Custom Actions calling Cloud Functions)March 2026RapidDev Engineering Team
TL;DR

Firebase Cloud Functions let you run server-side Node.js code triggered by HTTP calls, Firestore events, or schedules — all without a dedicated server. In FlutterFlow, you write the function code outside the visual editor (in the Firebase Functions directory), deploy via CLI, then call it from a FlutterFlow Custom Action using the firebase_functions package. Never write Cloud Function code in FlutterFlow's Custom Code panel — that panel is for client-side Dart only.

When You Need Cloud Functions in FlutterFlow

FlutterFlow handles most app logic visually — Firestore CRUD, auth, navigation, and simple API calls. Cloud Functions become necessary when you need server-side operations that the client cannot or should not perform: sending emails, processing payments, running expensive aggregations, using secret API keys securely, enforcing complex business logic that users should not be able to bypass, and reacting to database events. Understanding which function type to use for each scenario is the most important decision you'll make when adding server-side logic to a FlutterFlow app.

Prerequisites

  • A FlutterFlow project with Firebase connected and on the Blaze (pay-as-you-go) plan
  • Node.js 18+ and npm installed on your computer
  • Firebase CLI installed globally: npm install -g firebase-tools
  • Basic JavaScript familiarity — Cloud Functions are written in Node.js, not Dart

Step-by-step guide

1

Initialize Firebase Functions in your project directory

Export your FlutterFlow project to your computer and open a terminal in the project root directory. Run firebase login to authenticate, then firebase init functions. Select 'Use an existing project' and choose your Firebase project. Select JavaScript (not TypeScript for simplicity, though TypeScript is recommended for larger projects). Accept the default ESLint configuration. When initialization completes, a functions/ directory appears in your project containing index.js (where all functions are defined), package.json, and node_modules. Open functions/index.js — this is where all your Cloud Functions live. The file starts with: const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp();

Expected result: The functions/ directory exists with index.js, package.json, and a successfully installed node_modules folder.

2

Write an onCall function for secure client-triggered logic

onCall is the recommended function type for operations triggered by user actions in your app. It automatically handles authentication — you get the calling user's UID in context.auth.uid without any token verification code. Add a function to functions/index.js for a common use case like sending a welcome email or processing a payment. The function receives data (your custom parameters) and context (auth info). Always check context.auth is not null at the start of any onCall function that requires authentication. Return a plain JavaScript object — it's automatically serialized to JSON and returned to the calling app. Throw functions.https.HttpsError with a code and message for errors — these propagate back to the caller as structured exceptions.

index.js
1// functions/index.js — onCall function example
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4admin.initializeApp();
5
6// Called from FlutterFlow Custom Action
7exports.processUserAction = functions.https.onCall(
8 async (data, context) => {
9 // 1. Verify authentication
10 if (!context.auth) {
11 throw new functions.https.HttpsError(
12 'unauthenticated',
13 'You must be logged in to perform this action'
14 );
15 }
16 // 2. Validate input parameters
17 const { actionType, targetId } = data;
18 if (!actionType || !targetId) {
19 throw new functions.https.HttpsError(
20 'invalid-argument',
21 'actionType and targetId are required'
22 );
23 }
24 // 3. Perform server-side logic
25 const db = admin.firestore();
26 const userId = context.auth.uid;
27 await db.collection('userActions').add({
28 userId,
29 actionType,
30 targetId,
31 createdAt: admin.firestore.FieldValue.serverTimestamp(),
32 });
33 // 4. Return result
34 return { success: true, message: 'Action recorded' };
35 }
36);

Expected result: The function is defined in index.js. Running firebase deploy --only functions deploys it successfully and shows the function URL in the terminal output.

3

Add Firestore trigger and scheduled functions

Firestore triggers run automatically when documents are created, updated, or deleted — no client call needed. Use them for: sending notifications when a new message is created, cleaning up related data when a user is deleted, or computing aggregations when new records are added. Scheduled functions run on a cron schedule — use them for: sending weekly email digests, expiring old records, generating daily reports. Both types are added to the same index.js file. Scheduled functions require the Cloud Scheduler API to be enabled in your Google Cloud Console (it's enabled automatically when you deploy a scheduled function for the first time).

triggers_and_scheduled.js
1// Firestore trigger: runs when a new 'orders' document is created
2exports.onOrderCreated = functions.firestore
3 .document('orders/{orderId}')
4 .onCreate(async (snap, context) => {
5 const order = snap.data();
6 const messaging = admin.messaging();
7 // Get user's FCM token
8 const userDoc = await admin.firestore()
9 .collection('users')
10 .doc(order.userId)
11 .get();
12 const fcmToken = userDoc.data()?.fcmToken;
13 if (fcmToken) {
14 await messaging.send({
15 token: fcmToken,
16 notification: {
17 title: 'Order confirmed!',
18 body: `Order #${context.params.orderId} received`,
19 },
20 });
21 }
22 });
23
24// Scheduled: runs every day at 9am UTC
25exports.dailyCleanup = functions.pubsub
26 .schedule('0 9 * * *')
27 .timeZone('UTC')
28 .onRun(async (_context) => {
29 const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
30 const db = admin.firestore();
31 const old = await db
32 .collection('tempData')
33 .where('createdAt', '<', cutoff)
34 .get();
35 const batch = db.batch();
36 old.forEach((doc) => batch.delete(doc.ref));
37 await batch.commit();
38 console.log(`Cleaned up ${old.size} old records`);
39 });

Expected result: After deployment, the Firestore trigger fires automatically within 10-30 seconds of a matching document creation. The scheduled function runs on its cron schedule visible in Google Cloud Console.

4

Call Cloud Functions from FlutterFlow Custom Actions

Export your FlutterFlow project. In pubspec.yaml, ensure cloud_functions is listed as a dependency (it should be if Firebase is connected). Create a Custom Action in FlutterFlow named 'callProcessUserAction'. In the Custom Action code, import 'package:cloud_functions/cloud_functions.dart'. Create an instance: final functions = FirebaseFunctions.instance. Call the function using: final callable = functions.httpsCallable('processUserAction'). Pass data as a Map and await the result. Handle HttpsCallableException for error cases. Map the returned JSON data to your FlutterFlow variables by returning values as the appropriate Dart types.

call_cloud_function.dart
1// FlutterFlow Custom Action: callProcessUserAction
2import 'package:cloud_functions/cloud_functions.dart';
3
4Future<bool> callProcessUserAction(
5 String actionType,
6 String targetId,
7) async {
8 try {
9 final functions = FirebaseFunctions.instance;
10 final callable = functions.httpsCallable(
11 'processUserAction',
12 options: HttpsCallableOptions(
13 timeout: const Duration(seconds: 30),
14 ),
15 );
16 final result = await callable.call({
17 'actionType': actionType,
18 'targetId': targetId,
19 });
20 // result.data is the Map returned by your Cloud Function
21 return result.data['success'] == true;
22 } on FirebaseFunctionsException catch (e) {
23 // e.code contains the HttpsError code you threw
24 // e.message contains the human-readable message
25 print('Cloud Function error: ${e.code} - ${e.message}');
26 return false;
27 }
28}

Expected result: Calling the Custom Action from a FlutterFlow button tap executes the Cloud Function and returns the success boolean to the Action Flow.

5

Configure environment variables and secrets in Cloud Functions

Never hard-code API keys, database credentials, or secrets directly in Cloud Function code. Firebase offers two mechanisms: environment config (firebase functions:config:set) for non-sensitive values, and Secret Manager for sensitive values like API keys. For secrets, run: firebase functions:secrets:set SENDGRID_API_KEY. In your function code, access it with: process.env.SENDGRID_API_KEY (for 2nd gen functions) or functions.config().sendgrid.key (for 1st gen). Add --set-secrets=SENDGRID_API_KEY to your function definition for 2nd gen. For region configuration, set the region in your function: functions.region('us-central1') — match this to the region your Firestore is in to minimize latency.

secrets_example.js
1// Using Secret Manager with 2nd gen functions
2const { onCall } = require('firebase-functions/v2/https');
3const { defineSecret } = require('firebase-functions/params');
4
5const sendgridKey = defineSecret('SENDGRID_API_KEY');
6
7exports.sendWelcomeEmail = onCall(
8 { secrets: [sendgridKey] },
9 async (request) => {
10 if (!request.auth) {
11 throw new HttpsError('unauthenticated', 'Login required');
12 }
13 // Access secret value
14 const apiKey = sendgridKey.value();
15 // Use apiKey with SendGrid SDK...
16 return { sent: true };
17 }
18);

Expected result: Cloud Function deploys with secrets configured. The function can access the API key value without it appearing in your source code or logs.

Complete working example

cloud_functions_index.js
1// functions/index.js — Complete example with all function types
2// Run: firebase deploy --only functions
3
4const functions = require('firebase-functions');
5const admin = require('firebase-admin');
6admin.initializeApp();
7
8const db = admin.firestore();
9const messaging = admin.messaging();
10
11// ─── onCall: called from FlutterFlow Custom Action ───────────────────────────
12exports.processUserAction = functions.https.onCall(
13 async (data, context) => {
14 if (!context.auth) {
15 throw new functions.https.HttpsError(
16 'unauthenticated', 'Login required'
17 );
18 }
19 const { actionType, targetId } = data;
20 if (!actionType || !targetId) {
21 throw new functions.https.HttpsError(
22 'invalid-argument', 'Missing required parameters'
23 );
24 }
25 await db.collection('userActions').add({
26 userId: context.auth.uid,
27 actionType,
28 targetId,
29 createdAt: admin.firestore.FieldValue.serverTimestamp(),
30 });
31 return { success: true };
32 }
33);
34
35// ─── Firestore trigger: new order → push notification ────────────────────────
36exports.onOrderCreated = functions.firestore
37 .document('orders/{orderId}')
38 .onCreate(async (snap, context) => {
39 const order = snap.data();
40 const userDoc = await db.collection('users').doc(order.userId).get();
41 const fcmToken = userDoc.data()?.fcmToken;
42 if (!fcmToken) return null;
43 return messaging.send({
44 token: fcmToken,
45 notification: {
46 title: 'Order confirmed',
47 body: `Order #${context.params.orderId} is being processed`,
48 },
49 data: { orderId: context.params.orderId },
50 });
51 });
52
53// ─── Scheduled: daily cleanup of temp records ────────────────────────────────
54exports.dailyCleanup = functions.pubsub
55 .schedule('0 9 * * *')
56 .timeZone('UTC')
57 .onRun(async () => {
58 const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
59 const snap = await db
60 .collection('tempData')
61 .where('createdAt', '<', cutoff)
62 .get();
63 if (snap.empty) return null;
64 const batch = db.batch();
65 snap.forEach((doc) => batch.delete(doc.ref));
66 await batch.commit();
67 console.log(`Cleaned ${snap.size} records`);
68 return null;
69 });
70
71// ─── onRequest: public webhook endpoint ──────────────────────────────────────
72exports.stripeWebhook = functions.https.onRequest(
73 async (req, res) => {
74 if (req.method !== 'POST') {
75 return res.status(405).send('Method not allowed');
76 }
77 // Verify Stripe signature here
78 const event = req.body;
79 if (event.type === 'checkout.session.completed') {
80 const session = event.data.object;
81 await db.collection('payments').add({
82 sessionId: session.id,
83 amount: session.amount_total,
84 currency: session.currency,
85 customerId: session.customer,
86 createdAt: admin.firestore.FieldValue.serverTimestamp(),
87 });
88 }
89 res.status(200).json({ received: true });
90 }
91);

Common mistakes

Why it's a problem: Writing Cloud Function code in FlutterFlow's Custom Code panel

How to avoid: Cloud Functions are Node.js code written in the functions/index.js file of your Firebase project, outside FlutterFlow entirely. Use FlutterFlow's Custom Code panel only for Dart code that calls the already-deployed functions, using the cloud_functions Flutter package.

Why it's a problem: Not checking context.auth in onCall functions before performing privileged operations

How to avoid: Add if (!context.auth) { throw new functions.https.HttpsError('unauthenticated', 'Login required'); } as the first line of every onCall function that requires authentication.

Why it's a problem: Deploying all functions with firebase deploy instead of targeted deploys during development

How to avoid: Deploy only the function you're working on: firebase deploy --only functions:functionName. This deploys a single function in 30-60 seconds.

Best practices

  • Always return null from Firestore trigger and scheduled functions that don't need to return data — returning undefined causes warnings in Cloud Functions logs.
  • Set function memory and timeout explicitly for functions that do heavy work: functions.runWith({ memory: '512MB', timeoutSeconds: 120 }) for aggregation tasks.
  • Use Firestore transactions for any operation that reads then writes data based on the read value — without transactions, concurrent function executions can produce race conditions.
  • Log meaningful messages inside functions using console.log for success states and console.error for failures — these appear in Google Cloud Logging and are invaluable for debugging.
  • Use the Firebase Emulator Suite (firebase emulators:start) to test functions locally before deploying, completely avoiding billing for development and testing.
  • Deploy functions to the same GCP region as your Firestore database to minimize latency on database operations inside functions.
  • Add the RapidDev Engineering Team to your Firebase project as a viewer if you need expert help debugging a production function issue — having access to Cloud Logging is essential for diagnosis.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a FlutterFlow app with Firebase. I need a Firebase Cloud Function that sends a welcome email using SendGrid when a new user document is created in Firestore. The user document has fields: displayName, email, plan (free or premium). Write the complete Cloud Function code using the Firestore onCreate trigger, the SendGrid Node.js SDK, and Secret Manager for the API key. Include the sendgrid/mail npm install command and error handling.

FlutterFlow Prompt

I have a deployed Firebase Cloud Function named 'generateInvoice' that accepts: {orderId: string, userId: string} and returns: {invoiceUrl: string, invoiceId: string}. Write me a FlutterFlow Custom Action in Dart that calls this function using the cloud_functions package, handles the FirebaseFunctionsException error case, and returns the invoiceUrl string so I can open it in a WebView action in my app.

Frequently asked questions

Do I need the Firebase Blaze plan to use Cloud Functions?

Yes. Cloud Functions require the Blaze (pay-as-you-go) plan. The Spark (free) plan does not support Cloud Functions. However, Blaze includes a generous free tier: 2 million function invocations per month, 400,000 GB-seconds of compute, and 200,000 CPU-seconds. Most apps with under 10,000 active users stay within the free tier most months.

What is the difference between onCall and onRequest functions?

onCall functions are designed for app clients — they automatically verify Firebase Authentication tokens, handle CORS, and serialize/deserialize JSON. onRequest functions are raw HTTP endpoints — you handle everything manually and they can be called by any HTTP client (webhooks, cron jobs, external services). Use onCall for anything triggered from your app; use onRequest for webhooks from third-party services like Stripe.

How do I debug a Cloud Function that is failing in production?

Open Google Cloud Console → Cloud Logging → select your Firebase project. Filter by resource type 'Cloud Function' and look for console.error() output and unhandled exception traces. For faster iteration, test locally with the Firebase Emulator Suite (firebase emulators:start) which runs functions locally and shows logs in your terminal.

Can I call one Cloud Function from another Cloud Function?

Yes, using the Firebase Admin SDK's callable functions or via direct HTTP calls. However, calling functions from other functions creates complex dependency chains and makes error handling harder. As a general rule, prefer extracting shared logic into a regular JavaScript module that both functions import, rather than creating function-to-function call chains.

How long can a Cloud Function run before it times out?

First-generation functions have a maximum timeout of 540 seconds (9 minutes). Second-generation functions (recommended for new projects) support up to 3,600 seconds (60 minutes). Set the timeout explicitly in your function options — the default is 60 seconds for first-gen and 60 seconds for second-gen HTTP functions. For long-running tasks, consider breaking work into smaller batches and chaining function calls.

How do I handle sensitive data like payment information in Cloud Functions?

Never log sensitive data using console.log — Cloud Logging stores logs and they can be read by anyone with Cloud Logging access. Use Secret Manager for API keys. For PCI-compliant payment processing, never handle raw card numbers — use Stripe Elements or Payment Intents which handle card data entirely on Stripe's servers, and only pass payment intent IDs to your Cloud Functions.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.