Systems Library / Marketing Automation / How to Build a Multi-Account Ad Management Dashboard
Marketing Automation paid advertising

How to Build a Multi-Account Ad Management Dashboard

Manage multiple ad accounts across platforms from one unified interface.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

A multi-account ad management dashboard tool shows every client's performance on one screen. I manage 10+ ad accounts and this saved me from the daily ritual of logging into each one separately. One page, all accounts, real-time status.

The dashboard pulls from the same unified database your daily scripts populate. No new data sources needed.

What You Need Before Starting

Step 1: Build the Account Summary Query

import sqlite3
from datetime import datetime, timedelta

def get_all_accounts_summary(db_path):
    conn = sqlite3.connect(db_path)
    yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
    
    accounts = conn.execute("""
        SELECT account_name,
            SUM(spend) as spend,
            SUM(clicks) as clicks,
            SUM(leads) as leads,
            SUM(impressions) as impressions,
            CASE WHEN SUM(leads)>0 THEN SUM(spend)/SUM(leads) ELSE 0 END as cpl,
            CASE WHEN SUM(impressions)>0 THEN SUM(clicks)*100.0/SUM(impressions) ELSE 0 END as ctr
        FROM ad_daily
        WHERE date = ?
        GROUP BY account_name
        ORDER BY spend DESC
    """, (yesterday,)).fetchall()
    
    return [{"account": r[0], "spend": r[1], "clicks": r[2], "leads": r[3],
             "impressions": r[4], "cpl": round(r[5], 2), "ctr": round(r[6], 2)} for r in accounts]

def get_account_detail(db_path, account_name, days=7):
    conn = sqlite3.connect(db_path)
    start = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
    
    daily = conn.execute("""
        SELECT date, SUM(spend), SUM(leads),
            CASE WHEN SUM(leads)>0 THEN SUM(spend)/SUM(leads) ELSE 0 END
        FROM ad_daily WHERE account_name = ? AND date >= ?
        GROUP BY date ORDER BY date
    """, (account_name, start)).fetchall()
    conn.close()
    
    return [{"date": r[0], "spend": r[1], "leads": r[2], "cpl": round(r[3], 2)} for r in daily]

Step 2: Create the Dashboard Server

from flask import Flask, render_template_string, jsonify

app = Flask(__name__)
DB_PATH = "meta_ads.db"

DASHBOARD_TEMPLATE = """
<!DOCTYPE html>
<html>
<head><title>Ad Dashboard</title>
<style>
    body { font-family: -apple-system, sans-serif; background: #0a0a0a; color: #e5e7eb; padding: 2rem; }
    .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1rem; }
    .card { background: #111827; border: 1px solid #1f2937; border-radius: 8px; padding: 1.5rem; }
    .card h3 { margin: 0 0 1rem; color: #f3f4f6; }
    .metric { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid #1f2937; }
    .metric-value { font-weight: 600; color: #60a5fa; }
    .status-good { color: #34d399; }
    .status-bad { color: #f87171; }
    h1 { color: #f9fafb; margin-bottom: 2rem; }
</style>
</head>
<body>
    <h1>Ad Accounts Overview</h1>
    <div class="grid">
        {% for account in accounts %}
        <div class="card">
            <h3>{{ account.account }}</h3>
            <div class="metric"><span>Spend</span><span class="metric-value">${{ "%.2f"|format(account.spend) }}</span></div>
            <div class="metric"><span>Leads</span><span class="metric-value">{{ account.leads }}</span></div>
            <div class="metric"><span>CPL</span><span class="metric-value {{ 'status-good' if account.cpl < 25 else 'status-bad' }}">${{ "%.2f"|format(account.cpl) }}</span></div>
            <div class="metric"><span>CTR</span><span class="metric-value">{{ "%.2f"|format(account.ctr) }}%</span></div>
        </div>
        {% endfor %}
    </div>
</body>
</html>
"""

@app.route("/")
def dashboard():
    accounts = get_all_accounts_summary(DB_PATH)
    return render_template_string(DASHBOARD_TEMPLATE, accounts=accounts)

@app.route("/api/account/<account_name>")
def account_detail(account_name):
    detail = get_account_detail(DB_PATH, account_name)
    return jsonify(detail)

Step 3: Add Health Status Indicators

def get_account_health(db_path, account_name):
    conn = sqlite3.connect(db_path)
    
    yesterday_spend = conn.execute("""
        SELECT COALESCE(SUM(spend), 0) FROM ad_daily
        WHERE account_name = ? AND date = DATE('now', '-1 day')
    """, (account_name,)).fetchone()[0]
    
    avg_spend = conn.execute("""
        SELECT COALESCE(AVG(daily_spend), 0) FROM (
            SELECT SUM(spend) as daily_spend FROM ad_daily
            WHERE account_name = ? AND date >= DATE('now', '-7 days')
            GROUP BY date
        )
    """, (account_name,)).fetchone()[0]
    conn.close()
    
    if yesterday_spend == 0:
        return "offline"
    elif avg_spend > 0 and yesterday_spend < avg_spend * 0.5:
        return "warning"
    elif avg_spend > 0 and yesterday_spend > avg_spend * 1.5:
        return "spike"
    return "healthy"

Step 4: Add Quick Actions

@app.route("/api/pause/<campaign_id>", methods=["POST"])
def pause_campaign(campaign_id):
    import requests
    token = os.getenv("META_ACCESS_TOKEN")
    resp = requests.post(f"https://graph.facebook.com/v19.0/{campaign_id}",
        params={"access_token": token, "status": "PAUSED"})
    return jsonify(resp.json())

@app.route("/api/activate/<campaign_id>", methods=["POST"])
def activate_campaign(campaign_id):
    import requests
    token = os.getenv("META_ACCESS_TOKEN")
    resp = requests.post(f"https://graph.facebook.com/v19.0/{campaign_id}",
        params={"access_token": token, "status": "ACTIVE"})
    return jsonify(resp.json())

Step 5: Deploy and Access

# Run on your VPS
pip install flask gunicorn
gunicorn -b 0.0.0.0:8080 dashboard:app --daemon

Access via http://your-vps-ip:8080. For security, add basic auth or restrict to your IP.

One dashboard for everything. Click into any account for details. Pause or activate campaigns without opening Ads Manager.

What to Build Next

Add historical trend charts for each account. Then add a daily digest that screenshots the dashboard and sends it to Slack every morning.

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