Systems Library / Marketing Automation / How to Build a Cross-Platform Ad Performance Dashboard
Marketing Automation paid advertising

How to Build a Cross-Platform Ad Performance Dashboard

Combine Meta, Google, and TikTok ad data in one unified dashboard.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

A cross platform ad performance dashboard puts every ad account on one screen. No more switching between Meta Ads Manager, Google Ads, and TikTok. I built this for a client running ads on three platforms and it cut their weekly reporting time from 3 hours to 10 minutes.

The trick is normalizing the data. Each platform uses different field names for the same metrics. You fix that once in the ingestion layer, then everything downstream is clean.

What You Need Before Starting

Step 1: Create the Unified Schema

import sqlite3

def init_db(db_path="ads_unified.db"):
    conn = sqlite3.connect(db_path)
    conn.execute("""CREATE TABLE IF NOT EXISTS unified_ads (
        id INTEGER PRIMARY KEY,
        platform TEXT,
        account_name TEXT,
        campaign_name TEXT,
        adset_name TEXT,
        ad_name TEXT,
        date TEXT,
        spend REAL,
        impressions INTEGER,
        clicks INTEGER,
        conversions REAL,
        revenue REAL,
        cpc REAL,
        ctr REAL,
        cpa REAL,
        roas REAL,
        fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )""")
    conn.commit()
    return conn

Step 2: Normalize Data from Each Platform

def normalize_meta(raw_data, account_name):
    rows = []
    for ad in raw_data:
        spend = float(ad.get("spend", 0))
        clicks = int(ad.get("clicks", 0))
        conversions = sum(int(a["value"]) for a in ad.get("actions", []) if a["action_type"] in ["lead", "purchase"])
        revenue = sum(float(a["value"]) for a in ad.get("action_values", []) if a["action_type"] == "purchase")
        
        rows.append({
            "platform": "meta",
            "account_name": account_name,
            "campaign_name": ad.get("campaign_name", ""),
            "adset_name": ad.get("adset_name", ""),
            "ad_name": ad.get("ad_name", ""),
            "date": ad.get("date_start", ""),
            "spend": spend,
            "impressions": int(ad.get("impressions", 0)),
            "clicks": clicks,
            "conversions": conversions,
            "revenue": revenue,
            "cpc": spend / clicks if clicks > 0 else 0,
            "ctr": float(ad.get("ctr", 0)),
            "cpa": spend / conversions if conversions > 0 else 0,
            "roas": revenue / spend if spend > 0 else 0,
        })
    return rows

def normalize_google(raw_data, account_name):
    rows = []
    for row in raw_data:
        spend = row["cost_micros"] / 1_000_000
        clicks = row["clicks"]
        conversions = row["conversions"]
        
        rows.append({
            "platform": "google",
            "account_name": account_name,
            "campaign_name": row["campaign_name"],
            "adset_name": row.get("ad_group_name", ""),
            "ad_name": row.get("ad_name", ""),
            "date": row["date"],
            "spend": spend,
            "impressions": row["impressions"],
            "clicks": clicks,
            "conversions": conversions,
            "revenue": row.get("conversion_value", 0),
            "cpc": spend / clicks if clicks > 0 else 0,
            "ctr": (clicks / row["impressions"] * 100) if row["impressions"] > 0 else 0,
            "cpa": spend / conversions if conversions > 0 else 0,
            "roas": row.get("conversion_value", 0) / spend if spend > 0 else 0,
        })
    return rows

Step 3: Build the Summary Queries

def platform_summary(db_path, date):
    conn = sqlite3.connect(db_path)
    rows = conn.execute("""
        SELECT platform,
            SUM(spend) as total_spend,
            SUM(clicks) as total_clicks,
            SUM(conversions) as total_conversions,
            SUM(revenue) as total_revenue,
            CASE WHEN SUM(conversions) > 0 THEN SUM(spend)/SUM(conversions) ELSE 0 END as avg_cpa,
            CASE WHEN SUM(spend) > 0 THEN SUM(revenue)/SUM(spend) ELSE 0 END as avg_roas
        FROM unified_ads
        WHERE date = ?
        GROUP BY platform
    """, (date,)).fetchall()
    conn.close()
    return rows

def total_summary(db_path, start_date, end_date):
    conn = sqlite3.connect(db_path)
    row = conn.execute("""
        SELECT SUM(spend), SUM(clicks), SUM(conversions), SUM(revenue),
            CASE WHEN SUM(conversions) > 0 THEN SUM(spend)/SUM(conversions) ELSE 0 END,
            CASE WHEN SUM(spend) > 0 THEN SUM(revenue)/SUM(spend) ELSE 0 END
        FROM unified_ads
        WHERE date BETWEEN ? AND ?
    """, (start_date, end_date)).fetchone()
    conn.close()
    return row

Step 4: Serve the Dashboard

from flask import Flask, render_template_string
from datetime import datetime, timedelta

app = Flask(__name__)

DASHBOARD_HTML = """
<html><body style="font-family:sans-serif;background:#0a0a0a;color:#e5e7eb;padding:2rem;">
<h1>Ad Performance Dashboard</h1>
<h2>{{ date }}</h2>
<table style="width:100%;border-collapse:collapse;">
<tr style="border-bottom:1px solid #333;"><th>Platform</th><th>Spend</th><th>Clicks</th><th>Conversions</th><th>CPA</th><th>ROAS</th></tr>
{% for row in rows %}
<tr style="border-bottom:1px solid #222;">
<td>{{ row[0] }}</td><td>${{ "%.2f"|format(row[1]) }}</td><td>{{ row[2] }}</td>
<td>{{ row[3] }}</td><td>${{ "%.2f"|format(row[5]) }}</td><td>{{ "%.2f"|format(row[6]) }}x</td>
</tr>{% endfor %}
</table></body></html>
"""

@app.route("/")
def dashboard():
    yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
    rows = platform_summary("ads_unified.db", yesterday)
    return render_template_string(DASHBOARD_HTML, date=yesterday, rows=rows)

Step 5: Automate the Data Pull

Run all platform fetchers, then serve:

# Cron: pull all platform data at 6 AM, dashboard is fresh by 7 AM
0 6 * * * cd /path/to/project && python3 pull_all_platforms.py

One database. One dashboard. Every platform.

What to Build Next

Add date range filtering and campaign-level drill-downs. Then add an AI summary that explains what changed day over day and recommends actions.

Related Reading

Want this system built for your business?

Get a free assessment. We will map every system your business needs and show you the ROI.

Get Your Free Assessment

Related Systems