Build a dynamic ad display system in Bubble by creating an Ad Data Type that stores creatives, targeting rules, and schedule dates. Use conditional searches to rotate ads based on page context or user demographics, track impressions and clicks with workflow counters, and build an advertiser dashboard for managing campaigns — all without writing code.
Displaying Dynamic Ads in Your Bubble App
Whether you are building a content platform, a marketplace, or a community app, displaying ads is a common monetization strategy. This tutorial shows you how to build your own ad system in Bubble — storing ad creatives in the database, rotating them based on rules, tracking performance metrics, and giving advertisers a management interface. This is an alternative to embedding third-party ad networks like Google AdSense.
Prerequisites
- A Bubble account with an app open in the editor
- User authentication set up for advertisers and admins
- Basic understanding of Data Types, searches, and workflows
Step-by-step guide
Create the Ad and Campaign Data Types
Create the Ad and Campaign Data Types
Go to Data tab → Data types. Create 'AdCampaign' with fields: name (text), advertiser (User), start_date (date), end_date (date), budget (number), status (text: active/paused/ended), target_category (text). Create 'Ad' with fields: campaign (AdCampaign), image (image), headline (text), destination_url (text), placement (text: banner/sidebar/inline), impressions (number, default 0), clicks (number, default 0), is_active (yes/no).
Expected result: Two Data Types (AdCampaign and Ad) are created with fields for targeting, tracking, and management.
Build the ad display element on content pages
Build the ad display element on content pages
On the page where you want ads to appear, add a Group element sized for your ad placement (e.g., 728x90 for a banner). Set the Group's Type of content to 'Ad' and its Data source to: 'Do a search for Ads' with constraints: is_active = yes, placement = 'banner', and campaign's status = 'active', campaign's start_date <= Current date/time, campaign's end_date >= Current date/time. Add ':random item' at the end to select a random ad from eligible ones. Inside the group, add an Image element showing 'Parent group's Ad's image' and a Text element for the headline.
Pro tip: Use ':random item' for simple rotation. For weighted rotation based on budget, add a 'priority' number field and sort by it descending, then take the first item.
Expected result: A random active ad appears in the banner slot, respecting date ranges and status filters.
Track impressions when ads are displayed
Track impressions when ads are displayed
Add a 'Page is loaded' workflow (or a 'Do when condition is true' event that fires when the ad group's data source is not empty). In this workflow, add the action: 'Make changes to a thing' → Thing to change: the ad group's data source (the Ad displayed) → impressions = impressions + 1. This increments the impression counter each time the ad is shown to a user. To avoid counting the same user twice per session, add a custom state 'impression_tracked' (yes/no) and check it in the Only when condition.
Expected result: Each time an ad is displayed to a user, its impression count increases by one.
Track clicks when users interact with ads
Track clicks when users interact with ads
Make the ad group clickable by adding a workflow: 'When Group Ad is clicked.' In this workflow, add two actions in order: (1) Make changes to Parent group's Ad → clicks = clicks + 1, (2) Open an external website → URL = Parent group's Ad's destination_url. This records the click and then sends the user to the advertiser's landing page. The click-through rate (CTR) can be calculated as clicks / impressions in your reporting.
Expected result: Clicking an ad increments its click counter and redirects the user to the advertiser's destination URL.
Build a basic advertiser dashboard
Build a basic advertiser dashboard
Create a page called 'advertiser-dashboard.' Add a Repeating Group with Type 'AdCampaign' and Data source: 'Do a search for AdCampaigns where advertiser = Current User.' Each cell shows: campaign name, status, start/end dates, and aggregate metrics. For metrics, add Text elements showing: 'Do a search for Ads where campaign = Current cell's AdCampaign:each item's impressions:sum' for total impressions and similar for clicks. Add buttons for 'Pause Campaign' (Make changes → status = 'paused') and 'Edit Campaign.'
Expected result: Advertisers see all their campaigns with performance metrics and can pause or manage them.
Add category-based ad targeting
Add category-based ad targeting
To show relevant ads based on page content, pass a category context to your ad search. On a blog post page, for example, the post has a category field. Update your ad search constraint to include: target_category = Current Page Post's category (or target_category is empty for untargeted ads). Use 'Ignore empty constraints' if you want ads with no target_category to show everywhere. This ensures software ads appear on tech articles and food ads appear on recipe pages.
Expected result: Ads are filtered by category relevance, showing targeted ads on matching content pages and general ads elsewhere.
Complete working example
1DYNAMIC AD SYSTEM — DATA MODEL & WORKFLOWS2=============================================34Data Types:5 AdCampaign: name, advertiser (User), start_date, end_date,6 budget, status (active/paused/ended), target_category7 Ad: campaign (AdCampaign), image, headline, destination_url,8 placement (banner/sidebar/inline), impressions (number),9 clicks (number), is_active (yes/no)1011Ad Display Logic:12 Group Ad Banner (728x90)13 Type: Ad14 Data source: Search Ads where15 is_active = yes16 placement = 'banner'17 campaign's status = 'active'18 campaign's start_date <= now19 campaign's end_date >= now20 campaign's target_category = page context category21 :random item2223Impression Tracking:24 Trigger: Page is loaded (or Do when ad group data is not empty)25 Only when: page's impression_tracked is no26 Action 1: Make changes to Group Ad's Ad → impressions + 127 Action 2: Set state impression_tracked = yes2829Click Tracking:30 Trigger: When Group Ad is clicked31 Action 1: Make changes to Group Ad's Ad → clicks + 132 Action 2: Open external website → Ad's destination_url3334Advertiser Dashboard:35 Page: advertiser-dashboard36 RG: AdCampaigns where advertiser = Current User37 Metrics: sum of impressions, sum of clicks per campaign38 Actions: Pause/Resume campaign, Edit ads, View stats3940Admin Review:41 Page: admin-ads42 RG: All AdCampaigns sorted by creation date43 Actions: Approve/Reject campaigns, Flag policy violationsCommon mistakes when displaying dynamic ads in Bubble.io: Step-by-Step Guide
Why it's a problem: Not filtering ads by date range
How to avoid: Always include date constraints in your ad search: start_date <= Current date/time AND end_date >= Current date/time.
Why it's a problem: Counting impressions on every page reload for the same user
How to avoid: Use a custom state or session cookie check to track whether the impression has already been counted in the current session.
Why it's a problem: Opening the destination URL without recording the click first
How to avoid: Place the 'Make changes to Ad' action before the 'Open external website' action in the workflow sequence.
Best practices
- Always filter ads by active status, date range, and placement to show only relevant, current ads
- Track both impressions and clicks to calculate click-through rates for advertisers
- Use ':random item' for simple ad rotation or a priority field for weighted display
- Implement session-based impression deduplication to avoid inflating metrics
- Give advertisers a self-service dashboard to manage campaigns and view performance
- Add admin approval workflows so ads are reviewed before going live
- Use category targeting to show relevant ads based on page content context
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build a dynamic ad display system in Bubble.io. How do I create Data Types for campaigns and ads, display rotating ads based on targeting rules, track impressions and clicks, and build an advertiser dashboard?
Build an ad management system. Create AdCampaign and Ad data types with targeting and tracking fields. Display random active ads on content pages filtered by placement and date. Track impressions on page load and clicks when the ad is clicked. Build an advertiser dashboard showing campaign performance metrics.
Frequently asked questions
Should I build my own ad system or use Google AdSense?
Build your own if you want to sell ad space directly to advertisers and keep 100% of revenue. Use AdSense if you want automated ad filling without managing advertiser relationships. Many apps use both.
How do I prevent the same ad from showing repeatedly?
Store recently shown ad IDs in a custom state list. Add a constraint to your ad search: unique id is not in page's shown_ads list. After displaying an ad, add its ID to the list.
Can I charge advertisers based on impressions or clicks?
Yes. With impression and click tracking built in, you can calculate costs as cost-per-thousand-impressions (CPM) or cost-per-click (CPC). Deduct from the campaign budget accordingly using a backend workflow.
How do I handle ad-blockers?
Since your ads are served from Bubble's own database (not an external ad network), most ad-blockers will not detect them. Your custom ad system is inherently ad-blocker resistant.
Can I display video ads?
Yes. Add a 'video_url' field to the Ad Data Type and use a Video element or HTML5 video tag in the ad group. Set it to autoplay (muted) for a richer ad experience.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation