Add Google Analytics 4 Graphs to Your Custom Python Dashboard

Blog / JavaScript · March 5, 2015 · Updated June 10, 2026 · 7 min read
Add Google Analytics 4 Graphs to Your Custom Python Dashboard

To put Google Analytics graphs on your own dashboard in 2026 you query the Google Analytics 4 (GA4) Data API from your Python backend with a service account, then hand the returned rows to a JavaScript charting library such as Chart.js. The Universal Analytics approach the original version of this tutorial used — analytics.js, the gapi.analytics charts embed, and the Core Reporting API v3 — no longer works: Google shut Universal Analytics down on July 1, 2023 and removed access to UA data the following year. Everything below is the current GA4 path, shown end to end in Django or Flask.

If you would rather have this built and maintained for you, see our Python development services and web development services.

Key takeaways

  • Universal Analytics is dead. ga.js, analytics.js, the gapi.analytics charts embed, and Core Reporting API v3 were all retired with UA on July 1, 2023. Any old ga:sessions / ga:date code must be migrated to GA4.
  • Collection now uses gtag.js (or Google Tag Manager) with a Measurement ID (G-XXXXXXXXXX) and GA4's event-based data model — not the UA pageviews-and-sessions schema.
  • Reporting uses the GA4 Data API (analyticsdata.googleapis.com) through the official google-analytics-data Python client and a service account JSON key granted the Viewer role on the property.
  • Charting stays client-side: send the API rows to your template as JSON and draw them with Chart.js (Plotly or ApexCharts are good alternatives).
  • IDs changed too: GA4 uses a numeric property ID (e.g. properties/123456789), not the UA ga:12345 view ID.

What changed from Universal Analytics to GA4?

If you are following an older tutorial (including the first version of this post), almost every moving part has been replaced. Map the old names to the new ones before you write any code.

Aspect Universal Analytics (retired) Google Analytics 4 (current)
Status Sunset July 1, 2023; data access removed in 2024 Active; the only option for new properties
Tracking snippet analytics.js / ga.js gtag.js or Google Tag Manager
Identifier Tracking ID UA-XXXXXXX + view ga:12345 Measurement ID G-XXXXXXXXXX + numeric property ID
Reporting API Core Reporting API v3 + gapi.analytics embed GA4 Data API v1 (analyticsdata.googleapis.com)
Python auth oauth2client + SignedJwtAssertionCredentials google-auth service account + google-analytics-data
Data model Sessions + pageviews schema Event-based (every hit is an event)
Field names ga:sessions, ga:date activeUsers, screenPageViews, date

The practical change: you no longer authorize charts in the browser with an access token. Instead your Python server authenticates with a service account, queries the Data API, and the front end only draws charts from the JSON your view returns.

How do you collect the data with GA4?

First create a GA4 property in Google Analytics and a Web data stream, which gives you a Measurement ID that looks like G-XXXXXXXXXX. Add the gtag.js snippet to every page (or load it through Google Tag Manager):

<!-- GA4 collection: load gtag.js with your Measurement ID -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag() { dataLayer.push(arguments); }
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');

  // GA4 is event-based: send your own events whenever you like
  gtag('event', 'view_dashboard', { section: 'analytics' });
</script>

How do you query GA4 from Python?

Open the Google Cloud Console, enable the Google Analytics Data API, and create a service account with a JSON key. Copy the service account email (it ends in gserviceaccount.com) and, in GA4 Admin under Property access management, grant it the Viewer role on your property. You also need the numeric property ID from Admin → Property settings.

Install the official client with pip install google-analytics-data, then read a daily report:

# analytics.py - query the GA4 Data API with a service account
import os
from google.oauth2 import service_account
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import (
    DateRange,
    Dimension,
    Metric,
    RunReportRequest,
)

# Numeric GA4 property ID (Admin -> Property settings), NOT the UA "ga:12345" view.
GA4_PROPERTY_ID = os.environ["GA4_PROPERTY_ID"]
KEY_FILE = os.environ["GA4_SERVICE_ACCOUNT_FILE"]  # path to the JSON key

# Read-only scope is all a reporting dashboard needs.
SCOPES = ["https://www.googleapis.com/auth/analytics.readonly"]


def get_daily_report(start_date="30daysAgo", end_date="today"):
    """Return GA4 daily active users and page views.

    Replaces the old Universal Analytics ``get_access_token`` +
    ``gapi.analytics`` flow. Authentication happens server-side with a
    service account, so the browser never sees a token.

    :param str start_date: GA4 date or relative value (e.g. ``"7daysAgo"``).
    :param str end_date: GA4 date or relative value (e.g. ``"today"``).
    :returns: rows with ``date``, ``active_users`` and ``page_views``.
    :rtype: list[dict]
    """
    credentials = service_account.Credentials.from_service_account_file(
        KEY_FILE, scopes=SCOPES
    )
    client = BetaAnalyticsDataClient(credentials=credentials)

    request = RunReportRequest(
        property=f"properties/{GA4_PROPERTY_ID}",
        dimensions=[Dimension(name="date")],
        metrics=[Metric(name="activeUsers"), Metric(name="screenPageViews")],
        date_ranges=[DateRange(start_date=start_date, end_date=end_date)],
    )
    response = client.run_report(request)

    rows = []
    for row in response.rows:
        ga_date = row.dimension_values[0].value  # e.g. "20260131"
        rows.append(
            {
                "date": f"{ga_date[:4]}-{ga_date[4:6]}-{ga_date[6:]}",
                "active_users": int(row.metric_values[0].value),
                "page_views": int(row.metric_values[1].value),
            }
        )
    rows.sort(key=lambda r: r["date"])
    return rows

How do you wire it into a Django or Flask view?

Call get_daily_report() from a view and pass the rows to your template as a JSON string, so the browser can chart them. The same dashboard.html works for both frameworks because both Django templates and Jinja support the |safe filter.

# --- Django (views.py) ---
import json
from django.shortcuts import render
from .analytics import get_daily_report


def dashboard(request):
    rows = get_daily_report("30daysAgo", "today")
    return render(request, "dashboard.html", {"ga_rows": json.dumps(rows)})


# --- Flask (app.py) ---
import json
from flask import Flask, render_template
from analytics import get_daily_report

app = Flask(__name__)


@app.route("/dashboard")
def dashboard():
    rows = get_daily_report("30daysAgo", "today")
    return render_template("dashboard.html", ga_rows=json.dumps(rows))

How do you draw the graph with Chart.js?

Chart.js is the modern, lightweight default for this (Plotly and ApexCharts are good if you need richer interactivity). Load it from a CDN, read the JSON your view rendered, and draw a line chart:

<!-- dashboard.html (works with the Django and Flask views above) -->
<canvas id="ga-chart" height="120"></canvas>

<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
<script>
  // ga_rows is the JSON string the view rendered (|safe in Django and Jinja)
  const rows = {{ ga_rows|safe }};

  new Chart(document.getElementById('ga-chart'), {
    type: 'line',
    data: {
      labels: rows.map((r) => r.date),
      datasets: [
        { label: 'Active users', data: rows.map((r) => r.active_users) },
        { label: 'Page views', data: rows.map((r) => r.page_views) },
      ],
    },
    options: { responsive: true, scales: { y: { beginAtZero: true } } },
  });
</script>

Which GA4 dimensions and metrics map to the old reports?

GA4 renamed almost every field. These are the common ones for a traffic dashboard; the full list is in Google's GA4 Dimensions & Metrics Explorer.

You want UA name (gone) GA4 metric or dimension
Date axis ga:date date (dimension)
Users ga:users activeUsers, totalUsers
New users ga:newUsers newUsers
Sessions ga:sessions sessions
Page views ga:pageviews screenPageViews
Top pages ga:pagePath pagePath (dimension)
Traffic source ga:source sessionSource

To rebuild the "Last 7 / Last 30 days" dropdown from the original UA version, pass the selected value as the start_date argument to get_daily_report() and re-render — or expose a small JSON endpoint your front end calls when the dropdown changes.

What about e-commerce events?

If your dashboard also needs revenue, product views, and on-site search, GA4's event model handles those through its recommended e-commerce events (purchase, view_item, search). We cover capturing and pulling that data in tracking product sales, views, and searches with GA4 e-commerce analytics.

Frequently Asked Questions

Does the old Universal Analytics charting code still work?

No. Universal Analytics stopped collecting data on July 1, 2023, and Google removed access to UA reports and the Core Reporting API in 2024. The analytics.js snippet, the gapi.analytics charts embed, and ga:-prefixed metrics no longer return data, so any UA code must be migrated to GA4 and the GA4 Data API.

What is the difference between gtag.js and the GA4 Data API?

gtag.js (or Google Tag Manager) runs in the browser and collects events into your GA4 property. The GA4 Data API runs on your server and reads aggregated report data back out. A custom dashboard needs both: gtag.js to capture traffic and the Data API to pull it into your own charts.

Do I need a service account or OAuth for the GA4 Data API?

Use a service account for a server-rendered dashboard showing your own property's data: create a JSON key and grant that service account the Viewer role on the GA4 property. OAuth is only needed when you must act on behalf of individual end users, such as a multi-tenant tool reading each customer's own Analytics account.

Where do I find my GA4 property ID?

In Google Analytics go to Admin → Property settings; the property ID is the numeric value (for example 123456789) you pass as properties/123456789. It replaces the old UA view ID like ga:12345. The G-XXXXXXXXXX Measurement ID is only for collection with gtag.js, not for the Data API.

Is Chart.js the best library for a Python analytics dashboard?

Chart.js is the lightweight default and covers line, bar, and pie charts with minimal setup, which fits most dashboards. Choose Plotly when you need zoom, pan, and scientific charts, or ApexCharts for polished real-time widgets. All three accept the same JSON rows your Python view returns.

Does GA4 and the Data API cost money?

Standard GA4 and the GA4 Data API are free to use, subject to Google's per-property request quotas. Analytics 360 offers higher quotas for large enterprises. Building and hosting your dashboard is separate engineering effort, but Google's product itself carries no usage fee at the standard tier.

Share this article