Build a self-serve ad platform within your FlutterFlow app. Advertisers create campaigns in Firestore with creative images, target URL, placement type, budget, and targeting criteria. Ad display Components query active campaigns for the appropriate placement, track impressions and clicks in a Firestore subcollection, and use Cloud Functions to enforce budgets and pause campaigns when spend limits are reached. An admin dashboard shows performance metrics including click-through rate.
Building an In-App Ad Serving Platform in FlutterFlow
Instead of using third-party ad networks, you can build your own ad serving system for direct advertiser relationships. This tutorial creates campaign management, ad rendering for banner and interstitial placements, impression and click tracking with frequency capping, and budget enforcement via Cloud Functions.
Prerequisites
- A FlutterFlow project with Firestore and Cloud Functions configured
- Firebase Storage for ad creative images
- Understanding of Firestore queries and Cloud Functions triggers
- An existing app with pages where ads will be displayed
Step-by-step guide
Design the Firestore data model for campaigns and tracking
Design the Firestore data model for campaigns and tracking
Create an ad_campaigns collection with fields: advertiserId (String), title (String), imageUrl (String, the ad creative), targetUrl (String, where clicks go), placement (String: banner or interstitial), impressionCount (int, default 0), clickCount (int, default 0), budget (double, total spend limit), costPerClick (double), currentSpend (double, default 0), isActive (Boolean), targeting (Map with optional fields: categories array, regions array). Create an ad_impressions subcollection under each campaign: userId (String), timestamp (Timestamp), type (String: impression or click).
Expected result: Firestore has an ad_campaigns collection with tracking subcollections ready for impression and click data.
Build the campaign management page for advertisers
Build the campaign management page for advertisers
Create a CampaignManagement page. Display a ListView of the advertiser's campaigns showing: title, ad preview image, placement type badge, impressionCount, clickCount, click-through rate (clicks divided by impressions as percentage), currentSpend versus budget progress bar, and an active/paused Switch toggle. Add a Create Campaign form with: title TextField, image upload FlutterFlowUploadButton, target URL TextField, placement DropDown (banner or interstitial), budget TextField (number), costPerClick TextField (number), and targeting category ChoiceChips. On create, save to Firestore with isActive true.
Expected result: Advertisers can create and manage campaigns with performance metrics and active/paused controls.
Create the banner ad display Component
Create the banner ad display Component
Build a BannerAd Component that can be placed on any page. The Component runs a Backend Query on ad_campaigns where placement equals banner, isActive equals true, and optionally matches the page's category if targeting is set. Pick a random campaign from results using a Custom Function that generates a random index. Display the campaign's imageUrl in a Container with a GestureDetector. On display, create an ad_impression document with type 'impression' and increment impressionCount on the campaign. On tap, create an impression with type 'click', increment clickCount, increment currentSpend by costPerClick, and launch the targetUrl.
Expected result: Banner ads appear on pages, serve random active campaigns, and track impressions and clicks.
Implement frequency capping to limit user exposure
Implement frequency capping to limit user exposure
Without capping, the same user sees the same ad repeatedly, inflating impression counts. Add an App State variable adImpressionCounts (JSON Map, default empty) that tracks campaignId to impression count for the current session. Before displaying an ad, check if adImpressionCounts[campaignId] exceeds 3. If it does, skip that campaign and select the next one from the query results. Increment the counter after each display. For cross-session capping, store daily impression counts in a Firestore document under the user: users/{uid}/ad_caps/{campaignId} with count and date fields.
Expected result: Each user sees a maximum of 3 impressions per campaign per day, preventing metric inflation.
Build a Cloud Function to enforce budget limits
Build a Cloud Function to enforce budget limits
Create a Cloud Function that triggers on every new ad_impression document where type equals 'click'. The function reads the campaign's currentSpend and budget fields. If currentSpend plus costPerClick exceeds budget, set isActive to false on the campaign document, pausing it automatically. Send a notification to the advertiser that their campaign has reached its budget limit. This prevents overspend and ensures ads stop serving once the budget is exhausted.
1// Cloud Function: enforceBudget2import * as functions from 'firebase-functions';3import * as admin from 'firebase-admin';4admin.initializeApp();56export const enforceBudget = functions.firestore7 .document('ad_campaigns/{campaignId}/ad_impressions/{impressionId}')8 .onCreate(async (snap, context) => {9 const data = snap.data();10 if (data.type !== 'click') return;1112 const campaignRef = admin.firestore()13 .doc(`ad_campaigns/${context.params.campaignId}`);14 const campaign = (await campaignRef.get()).data();15 if (!campaign) return;1617 const newSpend = (campaign.currentSpend || 0) + campaign.costPerClick;18 const updates: any = { currentSpend: newSpend };1920 if (newSpend >= campaign.budget) {21 updates.isActive = false;22 }23 await campaignRef.update(updates);24 });Expected result: Campaigns automatically pause when their budget is exhausted, preventing overspend.
Build the admin analytics dashboard for ad performance
Build the admin analytics dashboard for ad performance
Create an AdAnalytics page showing aggregate metrics across all campaigns. Display total impressions, total clicks, overall CTR (click-through rate), and total revenue (sum of currentSpend across campaigns). Add a ListView of all campaigns sorted by CTR descending showing the top performers. For each campaign show: title, impressions, clicks, CTR percentage, spend vs budget bar. Optionally add a time filter (Today, This Week, This Month) that queries ad_impressions by timestamp range and recalculates metrics.
Expected result: An admin dashboard shows ad platform performance with per-campaign metrics and revenue tracking.
Complete working example
1FIRESTORE DATA MODEL:2 ad_campaigns/{campaignId}3 advertiserId: String4 title: String5 imageUrl: String (ad creative)6 targetUrl: String (click destination)7 placement: "banner" | "interstitial"8 impressionCount: int (default 0)9 clickCount: int (default 0)10 budget: double (total spend limit)11 costPerClick: double12 currentSpend: double (default 0)13 isActive: Boolean14 targeting: { categories: [String], regions: [String] }15 └── ad_impressions/{impressionId}16 userId: String17 timestamp: Timestamp18 type: "impression" | "click"1920APP STATE:21 adImpressionCounts: JSON Map = {} (campaignId → count per session)2223WIDGET TREE — BannerAd Component:24 Container (height: 80, full width, card style)25 Backend Query: ad_campaigns26 where placement == 'banner'27 where isActive == true28 → Custom Function: pickRandom + check frequency cap29 GestureDetector (onTap → log click + launch targetUrl)30 Stack31 ├── Image (campaign.imageUrl, BoxFit.cover)32 └── Positioned (top-right)33 Container (tiny 'Ad' label, semi-transparent)3435WIDGET TREE — Campaign Management:36 Column37 ├── Text ('My Campaigns', Headline Small)38 ├── ListView (advertiser's campaigns)39 │ └── Container (card)40 │ Row41 │ ├── Image (ad preview, 60x60)42 │ ├── Column43 │ │ ├── Text (title)44 │ │ ├── Text (impressions + ' views | ' + clicks + ' clicks')45 │ │ └── LinearPercentIndicator (currentSpend / budget)46 │ └── Switch (isActive toggle)47 └── Button 'Create Campaign' → form4849CLOUD FUNCTION: enforceBudget50 Trigger: ad_impressions onCreate51 Logic: if click → update currentSpend52 if currentSpend >= budget → set isActive = falseCommon mistakes
Why it's a problem: Counting impressions on every page load without frequency capping
How to avoid: Implement frequency capping: track impressions per user per campaign in App State or Firestore and limit to 3 per day.
Why it's a problem: Not enforcing budgets server-side with Cloud Functions
How to avoid: Use a Cloud Function triggered on every click impression to check and enforce the budget atomically on the server.
Why it's a problem: Serving ads without an 'Ad' label on the creative
How to avoid: Add a small semi-transparent 'Ad' label positioned in the corner of every ad creative using a Stack with a Positioned widget.
Best practices
- Implement frequency capping to limit impressions per user per campaign per day
- Enforce budgets server-side with Cloud Functions to prevent overspend
- Label all ad placements with a visible 'Ad' indicator for transparency
- Track both impressions and clicks separately for accurate CTR calculation
- Use random campaign selection from active campaigns for fair distribution
- Auto-pause campaigns when budget is exhausted via Cloud Function
- Show advertisers clear performance metrics: impressions, clicks, CTR, and spend
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build a custom in-app ad serving platform in FlutterFlow. Advertisers create campaigns with images, target URLs, budgets, and targeting. Banner ads display on pages, track impressions and clicks with frequency capping, and Cloud Functions enforce budgets. Show me the data model, ad display Component, and budget enforcement Cloud Function.
Create a campaign management page with a list of ad campaigns showing preview images, click counts, and budget progress bars. Add a create campaign form with image upload and budget fields.
Frequently asked questions
How do I implement interstitial (fullscreen) ads?
Show a fullscreen overlay using a Container with 100% width and height in a Stack at the page level. Display the ad creative with a Close button that appears after 3 seconds using a delayed Timer action. Track the impression on display and click if the user taps the creative.
Can I target ads based on user interests?
Yes. Store user interests on their user document. When querying campaigns, filter by matching the campaign's targeting.categories against the user's interests array.
How do I bill advertisers for their ad spend?
Use Stripe to pre-charge advertisers for their campaign budget. Deduct from the pre-paid balance on each click via Cloud Function. When the balance reaches zero, pause the campaign and prompt for top-up.
How do I prevent click fraud?
Track clicks per user per campaign. If a single user clicks the same ad more than 3 times per day, stop counting additional clicks toward spend. Log suspicious patterns for manual review.
Can I support video ads?
Yes. Add a videoUrl field to campaigns. Use FlutterFlowVideoPlayer for video ad creatives. Track a 'view' impression only after 3 seconds of playback, and a 'completed' event at the end.
Can RapidDev build an ad platform for my app?
Yes. RapidDev can build complete ad serving systems with campaign management, programmatic targeting, real-time bidding, fraud detection, and advertiser billing.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation