This project runs a local Flask dashboard that reads GA4 data through the Google Analytics Data API using numeric Property IDs (not G-XXXXXXX measurement IDs).
- macOS with
python3available - A GA4 property for each site you want to report on
- A Google Cloud service account key JSON file with access to the GA4 properties
For each GA4 website:
- Open Google Analytics.
- Go to
Admin. - In the Property column, open
Property Settings. - Copy the numeric
Property ID.
- Copy the example site map:
cp config/sites.example.json config/sites.json
- Edit
config/sites.jsonand replace placeholder IDs with your own numeric IDs. - Put your service account key file at:
credentials.json
If your key file is elsewhere, set:
export GOOGLE_APPLICATION_CREDENTIALS="/absolute/path/to/your-key.json"This project can be published to a public GitHub repo as long as you do not commit your local secrets.
Ignored automatically:
credentials.jsonconfig/sites.json.venv/__pycache__/
Safe files to commit:
config/sites.example.jsonREADME.mdserver.pytemplates/- launcher scripts
If someone clones the repo, they can create their own local setup without seeing your keys or site list.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python3 server.pyOpen:
start_dashboard.command: createsconfig/sites.jsonif missing, installs deps, opens the dashboard, starts server in that Terminal window.setup_sites.command: interactive wizard that asks how many sites to track and which IDs to use.edit_sites.command: opensconfig/sites.jsonin TextEdit.stop_dashboard.command: stops the server process listening on port5000.
Start/stop behavior:
- Double-click
start_dashboard.commandto run the server. - If port
5000is busy, the launcher automatically picks the next open localhost port. - Leave that Terminal open while using the dashboard.
- Stop with
Ctrl+Cin that Terminal, or double-clickstop_dashboard.command.
Fastest option:
- Double-click
setup_sites.command - Enter how many sites you want to track
- Enter each site's display name and GA4
Property ID - Optionally enter
domain,account_id,realtime_url, andlogo_url
Manual option:
Edit config/sites.json with this exact structure:
{
"www.example.com": "123456789",
"Store Dashboard": {
"property_id": "987654321",
"domain": "store.example.com",
"account_id": "270069835"
},
"Portfolio": {
"property_id": "456789123",
"logo_url": "https://example.com/path/to/logo.png",
"realtime_url": "https://analytics.google.com/analytics/web/#/a270069835p456789123/realtime/overview?params=_u..nav%3Dmaui"
}
}- Left side: any display name you want.
- Right side can be either:
- just a GA4 numeric
Property IDstring, or - an object with
property_idplus optionaldomain,logo_url,account_id, andrealtime_url.
- just a GA4 numeric
- Add as many sites as needed, separated by commas.
Logo behavior:
- If
logo_urlis set, that logo is used. - Else if
domainis set, the dashboard fetches a favicon from that domain. - Else if the site name looks like a domain (for example
example.com), it uses that automatically. - Otherwise it shows an initials badge.
Realtime link behavior:
- If
realtime_urlis set, that exact GA4 realtime URL is used. - Else if
account_idis set, the dashboard builds a realtime URL like#/aACCOUNTpPROPERTY/realtime/overview. - Otherwise it falls back to a property-based realtime GA4 URL.
For publishing:
- Keep your real
config/sites.jsonlocal only. - Commit only
config/sites.example.jsonwith fake placeholder values. - Keep your service account JSON out of the repo.
Total Combined Trafficchart (sum across all configured properties)- Site snapshot cards at the top with quick KPIs (
Live 30m,Today (std),Org 7d,Org 30d) Realtime Geomap at the top of the dashboard with anAll Sitesor per-site selector- SEO sanity line per site (
search sanity,organic share,robots status,sitemap presence) - Summary counters (
Organic 7d,Organic 30d,Searched Sites (7d),Robots Issues) - One chart per configured site (last 30 days active users)
Last updatedtimestamp plusRefresh nowbutton (auto-refresh every 5 minutes)- Link pills per site for
Open in GA4,Realtime GA4, androbots.txt
No sites configured: createconfig/sites.json.Live 30mdoes not matchToday (std):Live 30mis realtime users from the last 30 minutes.Today (std)is GA4 standard reporting and can lag before it catches up.
- Robots sanity flags (
blocked/missing/error):- open the
robots.txtpill on the card. - if blocked or missing, apply the recommendation shown in the card's SEO line.
- open the
Permission deniedor API auth errors:- confirm
credentials.jsonpath - ensure the service account has at least Viewer access in each GA4 property
- confirm
If a card shows Robots: blocked or Robots: missing, a safe starter robots file is:
User-agent: *
Allow: /
Sitemap: https://yourdomain.com/sitemap.xmlUse your real domain in the sitemap URL.