# How to Set Up a Custom Ad Serving Platform in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 25-30 min
- Compatibility: FlutterFlow Free+
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

### 2. 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.

### 3. 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.

### 4. 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.

### 5. 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.

```
// Cloud Function: enforceBudget
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();

export const enforceBudget = functions.firestore
  .document('ad_campaigns/{campaignId}/ad_impressions/{impressionId}')
  .onCreate(async (snap, context) => {
    const data = snap.data();
    if (data.type !== 'click') return;

    const campaignRef = admin.firestore()
      .doc(`ad_campaigns/${context.params.campaignId}`);
    const campaign = (await campaignRef.get()).data();
    if (!campaign) return;

    const newSpend = (campaign.currentSpend || 0) + campaign.costPerClick;
    const updates: any = { currentSpend: newSpend };

    if (newSpend >= campaign.budget) {
      updates.isActive = false;
    }
    await campaignRef.update(updates);
  });
```

**Expected result:** Campaigns automatically pause when their budget is exhausted, preventing overspend.

### 6. 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 code example

File: `FlutterFlow Ad Serving Platform Setup`

```text
FIRESTORE DATA MODEL:
  ad_campaigns/{campaignId}
    advertiserId: String
    title: String
    imageUrl: String (ad creative)
    targetUrl: String (click destination)
    placement: "banner" | "interstitial"
    impressionCount: int (default 0)
    clickCount: int (default 0)
    budget: double (total spend limit)
    costPerClick: double
    currentSpend: double (default 0)
    isActive: Boolean
    targeting: { categories: [String], regions: [String] }
    └── ad_impressions/{impressionId}
          userId: String
          timestamp: Timestamp
          type: "impression" | "click"

APP STATE:
  adImpressionCounts: JSON Map = {} (campaignId → count per session)

WIDGET TREE — BannerAd Component:
  Container (height: 80, full width, card style)
    Backend Query: ad_campaigns
      where placement == 'banner'
      where isActive == true
    → Custom Function: pickRandom + check frequency cap
    GestureDetector (onTap → log click + launch targetUrl)
      Stack
        ├── Image (campaign.imageUrl, BoxFit.cover)
        └── Positioned (top-right)
              Container (tiny 'Ad' label, semi-transparent)

WIDGET TREE — Campaign Management:
  Column
    ├── Text ('My Campaigns', Headline Small)
    ├── ListView (advertiser's campaigns)
    │     └── Container (card)
    │           Row
    │             ├── Image (ad preview, 60x60)
    │             ├── Column
    │             │     ├── Text (title)
    │             │     ├── Text (impressions + ' views | ' + clicks + ' clicks')
    │             │     └── LinearPercentIndicator (currentSpend / budget)
    │             └── Switch (isActive toggle)
    └── Button 'Create Campaign' → form

CLOUD FUNCTION: enforceBudget
  Trigger: ad_impressions onCreate
  Logic: if click → update currentSpend
         if currentSpend >= budget → set isActive = false
```

## Common mistakes

- **Counting impressions on every page load without frequency capping** — The same user seeing the same ad 50 times inflates impression metrics and burns through the advertiser's budget unfairly. CTR becomes misleadingly low. Fix: Implement frequency capping: track impressions per user per campaign in App State or Firestore and limit to 3 per day.
- **Not enforcing budgets server-side with Cloud Functions** — Client-side budget checks can be bypassed or delayed. A campaign can overspend significantly before the client pauses it. Fix: Use a Cloud Function triggered on every click impression to check and enforce the budget atomically on the server.
- **Serving ads without an 'Ad' label on the creative** — Users may mistake ads for app content. This violates advertising transparency standards and erodes user trust. Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-set-up-a-custom-ad-serving-platform-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-set-up-a-custom-ad-serving-platform-in-flutterflow
