Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

Facebook Ads

Connect FlutterFlow to Facebook Ads by routing Meta Marketing API calls through a Firebase Cloud Function or Supabase Edge Function that holds your System User access token. The token never touches the Flutter client bundle. Your FlutterFlow API Group calls the proxy, which queries the Meta Insights API and returns campaign performance data you parse and bind to dashboard widgets.

What you'll learn

  • Why the Meta System User token must live in a proxy rather than a FlutterFlow API Call header
  • How to create a System User access token in Meta Business Manager that never expires
  • How to deploy a Firebase Cloud Function that queries the Meta Insights API on behalf of FlutterFlow
  • How to parse the daily-row Insights response into a FlutterFlow Data Type and build a chart dashboard
  • How to cache Insights data in Firestore to stay within the 200-calls-per-hour-per-ad-account limit
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced15 min read2 to 3 hoursMarketingLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to Facebook Ads by routing Meta Marketing API calls through a Firebase Cloud Function or Supabase Edge Function that holds your System User access token. The token never touches the Flutter client bundle. Your FlutterFlow API Group calls the proxy, which queries the Meta Insights API and returns campaign performance data you parse and bind to dashboard widgets.

Quick facts about this guide
FactValue
ToolFacebook Ads
CategoryMarketing
MethodFlutterFlow API Call
DifficultyAdvanced
Time required2 to 3 hours
Last updatedJuly 2026

Build a Facebook Ads Dashboard in FlutterFlow

The Meta Marketing API's Insights endpoint lets you pull campaign-level, ad-set-level, and ad-level performance metrics — spend, impressions, clicks, CPM, CTR, ROAS — for any date range. For a FlutterFlow founder this means building a branded in-app dashboard where you or your clients see Facebook and Instagram campaign performance without opening Ads Manager. The API itself is free; you pay only for actual ad spend billed by Meta.

The architectural constraint that shapes this entire integration is client-side security. FlutterFlow generates a Flutter app that runs on the user's device. Anything placed in an API Call header — including a Meta access token — is compiled into the app binary. Anyone can inspect that binary. Meta's System User access token is especially sensitive because it grants read access to all campaigns, ad accounts, and audiences linked to your Business Manager. It must never appear in FlutterFlow's API Call configuration. Instead, you create a thin proxy — a Firebase Cloud Function or Supabase Edge Function — that holds the token in server environment variables and relays Insights requests on behalf of your app.

The standout advantage of the System User token (versus a standard OAuth user token) is longevity. Standard user tokens expire every 60 days and require a re-auth flow. A System User token from Meta Business Manager does not expire, which means your proxy keeps working indefinitely without any token refresh logic. The tradeoff is setup time: you need Business Manager admin access to create the System User, assign it the correct ad account permissions, and generate the token. Once done, the integration is stable. The Insights API caps at 200 calls per hour per ad account, shared across all sessions of your app — aggressive caching in Firestore or Supabase is mandatory for any app with multiple concurrent users.

Integration method

FlutterFlow API Call

FlutterFlow compiles to a Flutter client, so any token in an API Call header ships inside the app binary where it can be extracted. The Meta Marketing API System User access token is a secret — it must live in a Firebase Cloud Function or Supabase Edge Function proxy. FlutterFlow calls your proxy URL; the proxy appends the token and the Insights API fields to a request against https://graph.facebook.com, then returns daily campaign rows that FlutterFlow parses and displays. The System User token's key advantage: it does not expire after 60 days like a standard user token, so your proxy never needs a refresh loop.

Prerequisites

  • A Meta Business Manager account with at least one active ad account
  • Business Manager admin access to create a System User and generate a non-expiring access token
  • A Firebase project with Cloud Functions on Blaze plan or a Supabase project with Edge Functions enabled
  • Your ad account ID in the format act_XXXXXXXXXX (visible in Ads Manager URL or Business Settings)
  • A FlutterFlow project with an authenticated user flow already working

Step-by-step guide

1

Create a System User in Meta Business Manager and generate a non-expiring token

Go to business.facebook.com, open your Business Manager, and navigate to Business Settings → Users → System Users. Click Add and create a new system user — give it a descriptive name like 'FlutterFlow Dashboard'. Set the role to Employee (not Admin) unless you need write access. After creating the system user, click Add Assets, select Ad Accounts, choose your ad account, and grant at least Analyze permissions (read-only). Now click Generate New Token next to the system user. In the token dialog, select your app (or create a new Meta developer app at developers.facebook.com/apps) and check the ads_read permission scope at minimum. Generate the token. This token is non-expiring — it does not rotate after 60 days like a standard user OAuth token — which means your Firebase proxy will keep working indefinitely without any refresh logic. Copy the token immediately; you will store it only in your backend environment variables, never anywhere a client app can access. Copy your ad account ID as well (found in Business Settings → Ad Accounts, formatted as act_XXXXXXXXXX). You will pass the ad account ID as a variable from FlutterFlow; the token stays server-side.

Pro tip: Create a dedicated Meta developer app for this integration rather than using a personal app — it makes permission management and token ownership cleaner if you ever bring on team members.

Expected result: You have a System User with ads_read permission on your ad account, a non-expiring access token copied to a secure location, and your ad account ID ready for the next step.

2

Deploy a Firebase Cloud Function proxy for the Meta Insights API

In your Firebase project, create an HTTP Cloud Function that accepts ad account ID and date range parameters from FlutterFlow, appends the System User access token from environment variables, and queries the Meta Graph API Insights endpoint. The Meta Insights endpoint URL format is https://graph.facebook.com/v19.0/{ad_account_id}/insights. Your proxy must include CORS headers because FlutterFlow web builds run in a browser. The function requests only the fields your dashboard needs — requesting more fields wastes quota from the 200-calls-per-hour ceiling. Use time_increment=1 in the query parameters to get one data row per day, which is the right granularity for trend charts. After writing the function, deploy it from your local machine using the Firebase CLI. Copy the HTTPS trigger URL from the Firebase Console after deployment — this is what you will paste into FlutterFlow as the API Group Base URL. Store the access token using Firebase Secret Manager or the functions config system so it appears nowhere in your code. Alternatively, use a Supabase Edge Function with identical fetch logic — the Deno runtime and CORS headers work the same way, only the deployment method differs.

index.js
1// functions/index.js (Firebase Cloud Functions — Node.js)
2const functions = require('firebase-functions');
3const fetch = require('node-fetch');
4
5exports.facebookInsights = functions.https.onRequest(async (req, res) => {
6 res.set('Access-Control-Allow-Origin', '*');
7 res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
8 res.set('Access-Control-Allow-Headers', 'Content-Type');
9 if (req.method === 'OPTIONS') { res.status(204).send(''); return; }
10
11 const ACCESS_TOKEN = process.env.META_SYSTEM_USER_TOKEN;
12 const { ad_account_id, start_date, end_date, level } = req.body;
13
14 if (!ad_account_id || !start_date || !end_date) {
15 res.status(400).json({ error: 'Missing required fields' });
16 return;
17 }
18
19 const params = new URLSearchParams({
20 level: level || 'campaign',
21 fields: 'campaign_name,spend,impressions,clicks,ctr,cpm,reach',
22 time_range: JSON.stringify({ since: start_date, until: end_date }),
23 time_increment: '1',
24 limit: '100',
25 access_token: ACCESS_TOKEN
26 });
27
28 const metaRes = await fetch(
29 `https://graph.facebook.com/v19.0/${ad_account_id}/insights?${params}`
30 );
31 const data = await metaRes.json();
32 res.json(data);
33});
34

Pro tip: Add a check for the Meta error codes 17 and 32 in your function — these indicate the 200-calls/hour limit has been reached. Return a 429 to FlutterFlow so the app can display a 'Data temporarily unavailable' message rather than showing empty results.

Expected result: The Cloud Function is deployed and returns Meta Insights campaign data as JSON when called with a POST body containing ad_account_id, start_date, and end_date.

3

Create a FlutterFlow API Group pointing at your proxy

In FlutterFlow, open the left navigation panel and click API Calls. Click + Add and choose Create API Group. Name it MetaInsights and paste your Firebase Cloud Function HTTPS trigger URL as the Base URL. Do not add any Authorization headers to the API Group — the System User token lives on the server and never appears in FlutterFlow's configuration. Click + Add inside the group to create an individual API Call. Name it getCampaignInsights, set the method to POST, and leave the endpoint path blank since the Base URL is the full endpoint. Switch to the Body tab and select JSON. Add four body variables: ad_account_id (String), start_date (String), end_date (String), and level (String — default value 'campaign'). Your JSON body template: { "ad_account_id": "{{ad_account_id}}", "start_date": "{{start_date}}", "end_date": "{{end_date}}", "level": "{{level}}" }. Go to the Response & Test tab and fill in a real ad account ID (in act_XXXXXXXXXX format), real date values, and click Test. The response should include a data array with objects containing campaign_name, spend, impressions, clicks, and date_start. Click Generate from Response to auto-create JSON Path expressions. Review the generated paths and confirm they point to the correct fields inside the data array.

api_call_body.json
1{
2 "ad_account_id": "{{ad_account_id}}",
3 "start_date": "{{start_date}}",
4 "end_date": "{{end_date}}",
5 "level": "{{level}}"
6}

Pro tip: Use act_XXXXXXXXXX format for the ad_account_id in the test panel — omitting the 'act_' prefix returns a 'Invalid account ID' error from the Meta API.

Expected result: The getCampaignInsights API Call test returns a 200 response with a populated data array of campaign metrics in the FlutterFlow test panel.

4

Build the Data Type and bind Insights data to dashboard widgets

Create a new custom Data Type in FlutterFlow called CampaignInsight with these fields: campaignName (String), spend (Double), impressions (Integer), clicks (Integer), ctr (Double), cpm (Double), reach (Integer), dateStart (String). Return to your getCampaignInsights API Call and use the JSON Path section to map each path to the corresponding Data Type field: $.data[*].campaign_name to campaignName, $.data[*].spend to spend, $.data[*].impressions to impressions, $.data[*].date_start to dateStart, and so on. Note that Meta returns spend and ctr as strings in the Insights response — cast them to Double in FlutterFlow's type selector. Build a new dashboard screen. Add a Row at the top with a DateRangePicker widget or two date text fields so users can select the reporting period, storing the selected dates in AppState variables. Below that, add a ListView with its source set to Backend Query → getCampaignInsights, passing the ad_account_id from the authenticated user's data, and the date values from AppState. Inside each ListView item, add a Card with Text widgets bound to campaignName (bold, large) and a subtitle showing 'Spend: $' + spend formatted to two decimal places. Add a bar Chart widget above the list binding the spend series to the selected date range for a visual trend. For the ad account ID, store it in the user's Supabase or Firestore profile record and read it from the logged-in user's data so each user sees their own account.

Pro tip: Meta's Insights API returns spend as a string like '123.45' rather than a number — use FlutterFlow's type casting in the JSON Path binding to convert it to Double, or parse it in the proxy before returning.

Expected result: The dashboard screen shows a list of Facebook campaigns with spend, impressions, and CTR for the selected date range, loaded from your proxy.

5

Add Firestore caching to respect the 200-calls/hour Insights limit

The Meta Insights API enforces a hard limit of 200 calls per hour per ad account, shared across all users and sessions of your app. If ten users open the dashboard simultaneously without caching, that is ten API calls for the same data — and 20 requests per hour easily consumes 10% of your hourly quota. When the limit is reached, Meta returns error code 17 or 32: 'User request limit reached'. The solution is to cache the Insights response in Firestore inside your Cloud Function and serve cached data to concurrent sessions. Check the cache document before calling Meta; if it is under 30 minutes old, return it immediately. If stale, fetch fresh data, update Firestore, and return the new response. Document the cached_at timestamp in the response so the FlutterFlow app can display a 'Data as of {time}' indicator. Add a force_refresh Boolean body variable to the API Call so a pull-to-refresh gesture can bypass the cache when the user explicitly needs current numbers. Update your Cloud Function to implement the caching check as shown in the code block. If you want help setting up the caching layer or handling multiple ad accounts, RapidDev's team builds FlutterFlow integrations like this every week — free scoping call at rapidevelopers.com/contact.

index.js
1// Caching layer to add inside facebookInsights function
2const admin = require('firebase-admin');
3if (!admin.apps.length) admin.initializeApp();
4const db = admin.firestore();
5
6const { ad_account_id, start_date, end_date, force_refresh } = req.body;
7const cacheKey = `${ad_account_id}_${start_date}_${end_date}`;
8const cacheRef = db.collection('meta_insights_cache').doc(cacheKey);
9const cached = await cacheRef.get();
10
11if (cached.exists && !force_refresh) {
12 const { payload, cached_at } = cached.data();
13 const ageMs = Date.now() - cached_at;
14 if (ageMs < 30 * 60 * 1000) { // 30 minutes
15 res.json({ ...payload, from_cache: true, cached_at });
16 return;
17 }
18}
19
20// ... Meta API fetch ...
21// After receiving response:
22await cacheRef.set({ payload: metaData, cached_at: Date.now() });
23res.json({ ...metaData, from_cache: false });
24

Pro tip: If you have multiple ad accounts in the same Business Manager, each account has its own 200-calls/hour quota — use the ad_account_id as part of the Firestore cache key so each account's data is cached independently.

Expected result: Concurrent user sessions return cached campaign data immediately, and Meta API calls are reduced to at most one per 30 minutes per unique account-and-date-range combination.

Common use cases

Multi-client ad performance portal for a marketing agency

A marketing agency builds a FlutterFlow mobile app where each client logs in with their own account and sees their Facebook and Instagram campaign performance — spend, CPM, CTR, ROAS — refreshed every 30 minutes from a cached proxy. The System User token gives the agency access to all client ad accounts through one non-expiring credential.

FlutterFlow Prompt

Build a screen with a dropdown to select an ad account, then a list of campaigns below it showing spend and impressions for the last 30 days, loaded from a Firebase proxy.

Copy this prompt to try it in FlutterFlow

E-commerce app with built-in ad ROI monitoring

A DTC brand builds a FlutterFlow app that shows their Meta ad spend alongside Shopify revenue in a single dashboard, so the founder can check ROAS on their phone each morning without switching between platforms. Both data sources hit different proxy endpoints and merge into a unified summary screen.

FlutterFlow Prompt

Create a dashboard with a spend vs revenue bar chart for the last 14 days, where spend comes from the Facebook Ads proxy and revenue comes from a Supabase table.

Copy this prompt to try it in FlutterFlow

Lead-gen app tracking Facebook campaign performance by campaign name

A real estate agent builds a FlutterFlow app that shows which Facebook ad campaigns are generating the most lead form fills, with daily spend and lead count per campaign, updated each morning from the Insights API through a scheduled cache refresh.

FlutterFlow Prompt

Show a list of Facebook campaigns sorted by lead count descending, with spend and CPL for the current month, refreshed daily from a backend proxy.

Copy this prompt to try it in FlutterFlow

Troubleshooting

Meta API returns error code 17 or 32: 'User request limit reached'

Cause: The 200-calls-per-hour-per-ad-account Insights API ceiling has been exhausted. This happens when multiple users load the dashboard simultaneously without a caching layer, or when a developer tests the integration repeatedly in a short window.

Solution: Implement the Firestore caching layer described in Step 5. Serve cached data for at least 30 minutes to reduce upstream calls. If the error occurs despite caching, check that all paths through your proxy code actually read from the cache — a code bug might be bypassing it. Reduce the number of fields in the 'fields' query parameter to lessen response size and keep per-call weight lower.

'XMLHttpRequest error' in the FlutterFlow test panel or on web build

Cause: CORS is blocking the request. This happens when the FlutterFlow API Group Base URL points directly to graph.facebook.com instead of your Cloud Function proxy, or when the proxy is missing the Access-Control-Allow-Origin response header.

Solution: Ensure the Base URL in your FlutterFlow API Group is your Cloud Function URL, not graph.facebook.com. Confirm the function includes 'res.set("Access-Control-Allow-Origin", "*")' on every response path including error returns. Also confirm the function handles OPTIONS preflight with a 204 response and the same headers.

The Insights response data array is empty even though campaigns have spend

Cause: The date range passed to the Insights API falls outside any campaign activity, or the ad_account_id format is wrong (missing the 'act_' prefix), or the System User does not have Analyze permission on the correct ad account.

Solution: In Meta Business Settings → System Users, click your system user and check its Ad Account assets — confirm the specific ad account is listed with Analyze or higher permission. Verify the ad_account_id format is act_XXXXXXXXXX, not just the numeric ID. Test with a date range when you know campaigns were active and had spend.

Token works in Postman but Meta returns 190 'Invalid OAuth access token' from the proxy

Cause: The access token string contains extra whitespace, a newline character, or was truncated when copied from Business Manager. This is especially common when the token is set via an environment variable with trailing spaces.

Solution: In your Firebase function, add a console.log of ACCESS_TOKEN.length and compare it against the token length you copied. Trim whitespace: const token = process.env.META_SYSTEM_USER_TOKEN?.trim(). Re-set the environment variable being careful not to include any surrounding quotes or spaces in the value.

typescript
1const ACCESS_TOKEN = process.env.META_SYSTEM_USER_TOKEN?.trim();

Best practices

  • Use a System User token from Meta Business Manager rather than a standard OAuth user token — it never expires after 60 days and eliminates the need for a token refresh loop in your proxy.
  • Never place the System User token in a FlutterFlow API Call header — it compiles into the app binary and can be extracted by decompiling the APK or IPA.
  • Request only the Insights fields you actually display in your dashboard — the 'fields' parameter directly controls response size and API quota consumption.
  • Cache every Insights response for at least 15 to 30 minutes to stay within the 200-calls-per-hour-per-ad-account ceiling across concurrent users.
  • Include a 'Last updated' timestamp in your cached response and display it on the dashboard so users know data freshness at a glance.
  • Use time_increment=1 in Insights queries to get daily rows — it enables trend charts without making separate per-day requests.
  • For iOS 14.5+ in-app event attribution, implement App Tracking Transparency consent before firing any Meta pixel or conversion events — attribution is severely limited without it.
  • Create a dedicated Meta developer app for this integration so you can manage permissions independently from any personal apps or other projects.

Alternatives

Frequently asked questions

Why use a System User token instead of a regular Facebook login OAuth token?

A standard user OAuth token from the Facebook Login flow expires every 60 days and requires the user to re-authenticate. A System User token from Meta Business Manager is non-expiring — once generated, it works indefinitely without any refresh logic in your proxy. This makes your FlutterFlow dashboard integration maintenance-free once deployed, as long as the System User retains its ad account permissions.

Can I display data from multiple Facebook ad accounts in one FlutterFlow app?

Yes. Store each user's ad_account_id in your database linked to their authenticated profile. Pass the appropriate account ID to the proxy on each API call. The System User token can access multiple ad accounts as long as it has been granted Analyze permission on each one in Business Manager. Use the account ID as part of the Firestore cache key so each account's data is cached separately.

What happens if a user sees stale data because of caching?

Add a pull-to-refresh gesture on the dashboard screen and pass force_refresh: true in the API Call body to bypass the cache when the user explicitly requests a refresh. Also display a 'Last updated {timestamp}' label on the screen using the cached_at value returned by your proxy. Users quickly learn to expect near-real-time data with a manual refresh option rather than instant live data.

Can I use this integration to create or edit Facebook ads from within FlutterFlow?

The Meta Marketing API supports write operations like creating campaigns, ad sets, and ads, but building a full ad-creation interface is a significant project. You would need to add POST endpoints to your proxy for each object type and handle Meta's complex campaign hierarchy (campaign → ad set → ad → creative). Read-only reporting is the practical starting point for most FlutterFlow founders.

Do I need to handle iOS App Tracking Transparency for this integration?

ATT consent is required if you use the Meta SDK (facebook_app_events pub.dev package) for in-app event tracking. For the reporting-only use case covered in this tutorial — pulling Insights API data into a dashboard — ATT is not required because you are reading aggregated campaign statistics, not tracking individual user identities. ATT becomes relevant only when you fire pixel events or use the native Meta SDK for attribution.

RapidDev

Talk to an Expert

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

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

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.