DEV Community

Anthony Lee
Anthony Lee

Posted on

How to Set Up Google Analytics as a Claude Code Skill

A step-by-step guide for connecting your Google Analytics data to Claude Code, so you can ask questions about your website traffic in plain English.


What You'll End Up With

Once this is set up, you'll be able to open Claude Code (in VS Code or the terminal) and ask things like:

  • "How much traffic did my site get this week?"
  • "What are my top pages in the last 30 days?"
  • "Where is my traffic coming from?"
  • "Show me a daily breakdown of visitors this month"

Claude will run a script behind the scenes, pull your real Google Analytics data, and give you the answer.


What You'll Need Before Starting

  • A Google Analytics 4 (GA4) property (the newer version of Google Analytics)
  • A Google account with access to that GA4 property
  • Claude Code installed (either the VS Code extension or the CLI)
  • Python installed on your computer

Part 1: Set Up Google Cloud

Google Analytics data is accessed through Google's cloud platform. You need to create a "service account" - think of it as a robot employee that has read-only access to your analytics data.

Step 1: Go to Google Cloud Console

Open your browser and go to: https://console.cloud.google.com

Sign in with the same Google account that has access to your Google Analytics.

Step 2: Create a Project (or select an existing one)

If you've never used Google Cloud before:

  1. Click the project dropdown at the top of the page (it might say "Select a project")
  2. Click New Project
  3. Give it a name (something like "My Analytics" or your business name)
  4. Click Create
  5. Wait a moment, then select your new project from the dropdown

If you already have a project, just make sure it's selected.

Step 3: Enable the Google Analytics API

  1. In the search bar at the top of Google Cloud Console, type: Google Analytics Data API
  2. Click on Google Analytics Data API in the results
  3. Click the big blue Enable button
  4. Wait for it to activate (takes a few seconds)

Step 4: Create a Service Account

  1. In the left sidebar, click IAM & Admin
  2. Click Service Accounts
  3. Click + Create Service Account at the top
  4. Fill in the details:
    • Service account name: claude-ga4-reader (or whatever you like)
    • The email will auto-generate - it will look something like: claude-ga4-reader@your-project.iam.gserviceaccount.com
  5. Click Create and Continue
  6. Skip the "Grant this service account access" step - click Continue
  7. Skip the "Grant users access" step - click Done

Step 5: Download the Credentials Key

  1. You should now see your new service account in the list - click on it
  2. Go to the Keys tab
  3. Click Add KeyCreate new key
  4. Select JSON and click Create
  5. A .json file will download to your computer - this is important, don't lose it
  6. Move this file somewhere safe and permanent on your computer (for example: C:\Users\YourName\keys\ or your Desktop)

Write down the full file path - you'll need it later. It will look something like:

C:\Users\YourName\Downloads\your-project-name-abc123.json
Enter fullscreen mode Exit fullscreen mode

Also write down the service account email from step 4 - you'll need it in the next section.


Part 2: Give the Service Account Access to Your Analytics

The service account exists, but it doesn't have permission to see your analytics data yet. You need to invite it.

Step 6: Open Google Analytics

Go to: https://analytics.google.com

Step 7: Add the Service Account as a Viewer

  1. Click the gear icon (⚙️) in the bottom-left corner to open Admin
  2. In the Property column (the middle column), click Property Access Management
  3. Click the + button in the top-right → Add users
  4. In the email field, paste your service account email (the one that looks like claude-ga4-reader@your-project.iam.gserviceaccount.com)
  5. Set the role to Viewer (this is read-only — it can't change anything)
  6. Uncheck "Notify new users by email" (it's not a real email address)
  7. Click Add

Step 8: Find Your GA4 Property ID

While you're still in the Admin area:

  1. In the Property column, click Property Settings (or Property details)
  2. Look for Property ID - it's a number like 363186564
  3. Write this number down - you'll need it soon

Part 3: Install the Python Package

The skill uses a Python library to talk to Google's servers. You need to install it once.

Step 9: Open a Terminal

  • On Windows: Press Win + R, type cmd, press Enter
  • On Mac: Open the Terminal app (search for "Terminal" in Spotlight)

Step 10: Install the Package

Type this command and press Enter:

pip install google-analytics-data
Enter fullscreen mode Exit fullscreen mode

Wait for it to finish. You should see "Successfully installed" at the end.

If you get an error about pip not being found, try:

pip3 install google-analytics-data
Enter fullscreen mode Exit fullscreen mode

Part 4: Create the Skill Files

A Claude Code skill is just a folder with specific files in it. You need three files.

Step 11: Create the Skill Folder

Navigate to your Claude configuration folder and create the skill directory:

  • On Windows: C:\Users\YourName\.claude\skills\google-analytics\
  • On Mac/Linux: ~/.claude/skills/google-analytics/

If the skills folder doesn't exist yet, create it.

Step 12: Create SKILL.md

Inside the google-analytics folder, create a file called SKILL.md and paste the following content. This file tells Claude what the skill does and how to use it:

---
name: google-analytics
description: Query Google Analytics 4 data. Use when the user asks about website traffic, page views, sessions, user counts, conversions, top pages, traffic sources, or any analytics/metrics questions. Trigger on keywords like "analytics", "traffic", "visitors", "page views", "sessions", "GA4", "bounce rate", "conversions", "top pages", "referrals".
---

# Google Analytics 4 Skill

Query GA4 property data using the Google Analytics Data API v1.

## Setup

- **Credentials**: Service account JSON key at `YOUR_CREDENTIALS_PATH_HERE`
- **Property ID**: `YOUR_PROPERTY_ID_HERE`
- **Python dependency**: `google-analytics-data` (install if needed: `pip install google-analytics-data`)

## How to Use

Run the query script from this skill's directory:

~~~bash
python PATH_TO_SKILL/ga_query.py --report <report_type> [options]
~~~

## Available Reports

### 1. `overview` - High-level summary
~~~bash
python ga_query.py --report overview --days 30
~~~
Returns: total users, sessions, page views, avg session duration, bounce rate, new vs returning users.

### 2. `pages` - Top pages by views
~~~bash
python ga_query.py --report pages --days 30 --limit 20
~~~
Returns: page path, title, views, users, avg engagement time.

### 3. `sources` - Traffic sources
~~~bash
python ga_query.py --report sources --days 30 --limit 20
~~~
Returns: source, medium, sessions, users, conversions.

### 4. `countries` - Geographic breakdown
~~~bash
python ga_query.py --report countries --days 30 --limit 20
~~~
Returns: country, sessions, users, engagement rate.

### 5. `devices` - Device category breakdown
~~~bash
python ga_query.py --report devices --days 30
~~~
Returns: device category (desktop/mobile/tablet), sessions, users.

### 6. `daily` - Day-by-day trend
~~~bash
python ga_query.py --report daily --days 30
~~~
Returns: date, users, sessions, page views per day.

### 7. `realtime` - Active users right now
~~~bash
python ga_query.py --report realtime
~~~
Returns: active users in last 30 minutes by source.

### 8. `custom` - Custom query (advanced)
~~~bash
python ga_query.py --report custom --metrics "sessions,totalUsers" --dimensions "city" --days 7 --limit 10
~~~
Pass any valid GA4 API metric/dimension names as comma-separated values.

## Common Options

| Option | Default | Description |
|--------|---------|-------------|
| `--days` | `30` | Lookback period in days |
| `--limit` | `10` | Max rows returned |
| `--start` | — | Explicit start date (YYYY-MM-DD), overrides --days |
| `--end` | — | Explicit end date (YYYY-MM-DD), defaults to today |
| `--output` | `table` | Output format: `table`, `json`, or `csv` |

## GA4 Metric and Dimension Reference (for custom queries)

**Common Metrics**: totalUsers, newUsers, sessions, screenPageViews, averageSessionDuration, bounceRate, engagementRate, conversions, eventCount, activeUsers

**Common Dimensions**: date, pagePath, pageTitle, sessionSource, sessionMedium, country, city, deviceCategory, browser, operatingSystem, landingPage, sessionDefaultChannelGroup
Enter fullscreen mode Exit fullscreen mode

IMPORTANT - Replace the placeholders:

  • Replace YOUR_CREDENTIALS_PATH_HERE with the full path to your JSON key file from Step 5
  • Replace YOUR_PROPERTY_ID_HERE with your GA4 Property ID from Step 8
  • Replace PATH_TO_SKILL with the full path to your skill folder

Step 13: Create ga_query.py

In the same google-analytics folder, create a file called ga_query.py and paste the following Python script.

Before pasting, you need to update two values at the top of the file:

  • CREDENTIALS_PATH - the full path to your JSON key file from Step 5
  • PROPERTY_ID - your GA4 Property ID from Step 8
#!/usr/bin/env python3
"""
Google Analytics 4 Data API query tool.
Queries GA4 property data using a service account.
"""

import argparse
import json
import sys
import os
from datetime import datetime, timedelta

# ============================================================
# CONFIGURATION - UPDATE THESE TWO VALUES
# ============================================================
CREDENTIALS_PATH = r"C:\Users\YourName\path\to\your-credentials.json"
PROPERTY_ID = "000000000"
# ============================================================

def get_client():
    """Create and return a GA4 BetaAnalyticsDataClient."""
    os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = CREDENTIALS_PATH
    from google.analytics.data_v1beta import BetaAnalyticsDataClient
    return BetaAnalyticsDataClient()


def build_request(metrics, dimensions, days=30, start=None, end=None, limit=10, order_by_metric=None, desc=True):
    """Build a RunReportRequest."""
    from google.analytics.data_v1beta.types import (
        RunReportRequest, Metric, Dimension, DateRange, OrderBy
    )

    end_date = end or datetime.now().strftime("%Y-%m-%d")
    if start:
        start_date = start
    else:
        start_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")

    request = RunReportRequest(
        property=f"properties/{PROPERTY_ID}",
        metrics=[Metric(name=m.strip()) for m in metrics],
        dimensions=[Dimension(name=d.strip()) for d in dimensions] if dimensions else [],
        date_ranges=[DateRange(start_date=start_date, end_date=end_date)],
        limit=limit,
    )

    if order_by_metric:
        request.order_bys = [
            OrderBy(
                metric=OrderBy.MetricOrderBy(metric_name=order_by_metric),
                desc=desc,
            )
        ]

    return request


def format_response(response, output="table"):
    """Format the API response into the desired output format."""
    headers = [h.name for h in response.dimension_headers] + [h.name for h in response.metric_headers]
    rows = []
    for row in response.rows:
        values = [dv.value for dv in row.dimension_values] + [mv.value for mv in row.metric_values]
        rows.append(values)

    if output == "json":
        result = []
        for row in rows:
            result.append(dict(zip(headers, row)))
        return json.dumps(result, indent=2)

    elif output == "csv":
        lines = [",".join(headers)]
        for row in rows:
            lines.append(",".join(row))
        return "\n".join(lines)

    else:  # table
        col_widths = [len(h) for h in headers]
        for row in rows:
            for i, val in enumerate(row):
                col_widths[i] = max(col_widths[i], len(str(val)))

        separator = "+" + "+".join("-" * (w + 2) for w in col_widths) + "+"
        header_row = "|" + "|".join(f" {h:<{col_widths[i]}} " for i, h in enumerate(headers)) + "|"

        lines = [separator, header_row, separator]
        for row in rows:
            line = "|" + "|".join(f" {str(v):<{col_widths[i]}} " for i, v in enumerate(row)) + "|"
            lines.append(line)
        lines.append(separator)

        if response.row_count:
            lines.append(f"\nTotal rows: {response.row_count}")

        return "\n".join(lines)


def report_overview(client, args):
    """High-level site overview."""
    request = build_request(
        metrics=["totalUsers", "newUsers", "sessions", "screenPageViews",
                 "averageSessionDuration", "engagementRate", "bounceRate"],
        dimensions=[],
        days=args.days, start=args.start, end=args.end, limit=1,
    )
    response = client.run_report(request)
    return format_response(response, args.output)


def report_pages(client, args):
    """Top pages by views."""
    request = build_request(
        metrics=["screenPageViews", "totalUsers", "averageSessionDuration"],
        dimensions=["pagePath", "pageTitle"],
        days=args.days, start=args.start, end=args.end, limit=args.limit,
        order_by_metric="screenPageViews",
    )
    response = client.run_report(request)
    return format_response(response, args.output)


def report_sources(client, args):
    """Traffic sources."""
    request = build_request(
        metrics=["sessions", "totalUsers", "engagementRate", "conversions"],
        dimensions=["sessionSource", "sessionMedium"],
        days=args.days, start=args.start, end=args.end, limit=args.limit,
        order_by_metric="sessions",
    )
    response = client.run_report(request)
    return format_response(response, args.output)


def report_countries(client, args):
    """Geographic breakdown."""
    request = build_request(
        metrics=["sessions", "totalUsers", "engagementRate"],
        dimensions=["country"],
        days=args.days, start=args.start, end=args.end, limit=args.limit,
        order_by_metric="sessions",
    )
    response = client.run_report(request)
    return format_response(response, args.output)


def report_devices(client, args):
    """Device category breakdown."""
    request = build_request(
        metrics=["sessions", "totalUsers", "engagementRate"],
        dimensions=["deviceCategory"],
        days=args.days, start=args.start, end=args.end, limit=args.limit,
        order_by_metric="sessions",
    )
    response = client.run_report(request)
    return format_response(response, args.output)


def report_daily(client, args):
    """Day-by-day trend."""
    request = build_request(
        metrics=["totalUsers", "sessions", "screenPageViews"],
        dimensions=["date"],
        days=args.days, start=args.start, end=args.end, limit=args.days or 30,
    )
    from google.analytics.data_v1beta.types import OrderBy
    request.order_bys = [
        OrderBy(dimension=OrderBy.DimensionOrderBy(dimension_name="date"), desc=False)
    ]
    response = client.run_report(request)
    return format_response(response, args.output)


def report_realtime(client, args):
    """Realtime active users."""
    from google.analytics.data_v1beta.types import (
        RunRealtimeReportRequest, Metric, Dimension
    )
    request = RunRealtimeReportRequest(
        property=f"properties/{PROPERTY_ID}",
        metrics=[Metric(name="activeUsers")],
        dimensions=[Dimension(name="unifiedScreenName")],
        limit=args.limit,
    )
    response = client.run_realtime_report(request)

    headers = [h.name for h in response.dimension_headers] + [h.name for h in response.metric_headers]
    rows = []
    for row in response.rows:
        values = [dv.value for dv in row.dimension_values] + [mv.value for mv in row.metric_values]
        rows.append(values)

    if not rows:
        return "No active users right now."

    if args.output == "json":
        result = [dict(zip(headers, row)) for row in rows]
        return json.dumps(result, indent=2)
    elif args.output == "csv":
        lines = [",".join(headers)]
        for row in rows:
            lines.append(",".join(row))
        return "\n".join(lines)
    else:
        col_widths = [len(h) for h in headers]
        for row in rows:
            for i, val in enumerate(row):
                col_widths[i] = max(col_widths[i], len(str(val)))
        separator = "+" + "+".join("-" * (w + 2) for w in col_widths) + "+"
        header_row = "|" + "|".join(f" {h:<{col_widths[i]}} " for i, h in enumerate(headers)) + "|"
        lines = [separator, header_row, separator]
        for row in rows:
            line = "|" + "|".join(f" {str(v):<{col_widths[i]}} " for i, v in enumerate(row)) + "|"
            lines.append(line)
        lines.append(separator)
        return "\n".join(lines)


def report_custom(client, args):
    """Custom query with user-specified metrics and dimensions."""
    if not args.metrics:
        return "Error: --metrics required for custom report (comma-separated)"

    metrics = [m.strip() for m in args.metrics.split(",")]
    dimensions = [d.strip() for d in args.dimensions.split(",")] if args.dimensions else []

    request = build_request(
        metrics=metrics,
        dimensions=dimensions,
        days=args.days, start=args.start, end=args.end, limit=args.limit,
        order_by_metric=metrics[0],
    )
    response = client.run_report(request)
    return format_response(response, args.output)


REPORTS = {
    "overview": report_overview,
    "pages": report_pages,
    "sources": report_sources,
    "countries": report_countries,
    "devices": report_devices,
    "daily": report_daily,
    "realtime": report_realtime,
    "custom": report_custom,
}


def main():
    parser = argparse.ArgumentParser(description="Query Google Analytics 4 data")
    parser.add_argument("--report", required=True, choices=REPORTS.keys(), help="Report type")
    parser.add_argument("--days", type=int, default=30, help="Lookback period in days (default: 30)")
    parser.add_argument("--start", help="Start date (YYYY-MM-DD), overrides --days")
    parser.add_argument("--end", help="End date (YYYY-MM-DD), defaults to today")
    parser.add_argument("--limit", type=int, default=10, help="Max rows (default: 10)")
    parser.add_argument("--output", choices=["table", "json", "csv"], default="table", help="Output format")
    parser.add_argument("--metrics", help="Comma-separated metrics (for custom report)")
    parser.add_argument("--dimensions", help="Comma-separated dimensions (for custom report)")

    args = parser.parse_args()

    try:
        client = get_client()
        result = REPORTS[args.report](client, args)
        print(result)
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Step 14: Create requirements.txt

In the same folder, create a file called requirements.txt with just this one line:

google-analytics-data>=0.18.0
Enter fullscreen mode Exit fullscreen mode

This file is just for reference - it documents what Python package the skill needs.


Part 5: Verify Your Setup

Step 15: Check Your Folder Structure

Your skill folder should now look like this:

.claude/
  skills/
    google-analytics/
      SKILL.md
      ga_query.py
      requirements.txt
Enter fullscreen mode Exit fullscreen mode

On Windows, the full path would be:

C:\Users\YourName\.claude\skills\google-analytics\SKILL.md
C:\Users\YourName\.claude\skills\google-analytics\ga_query.py
C:\Users\YourName\.claude\skills\google-analytics\requirements.txt
Enter fullscreen mode Exit fullscreen mode

Step 16: Test the Script Manually (Optional but Recommended)

Before trying it in Claude Code, you can test the script directly to make sure everything is connected:

  1. Open a terminal
  2. Navigate to the skill folder:
   cd C:\Users\YourName\.claude\skills\google-analytics
Enter fullscreen mode Exit fullscreen mode
  1. Run:
   python ga_query.py --report overview --days 7
Enter fullscreen mode Exit fullscreen mode

If everything is set up correctly, you should see a table with your analytics data. If you get an error, check that:

  • The credentials path in ga_query.py is correct
  • The property ID is correct
  • You added the service account email as a Viewer in Google Analytics (Step 7)
  • You installed the Python package (Step 10)

Step 17: Test in Claude Code

  1. Open Claude Code (restart it if it was already running)
  2. Ask something like: "How much traffic did my site get in the last 7 days?"
  3. Claude should recognize the analytics-related question, load the skill, and run the script

If Claude doesn't pick it up automatically, try being explicit: "Use the google-analytics skill to show me a traffic overview for the last 30 days."


Troubleshooting

"ModuleNotFoundError: No module named 'google'"
→ The Python package isn't installed. Run: pip install google-analytics-data

"Permission denied" or "403" errors
→ The service account doesn't have access to your GA4 property. Go back to Step 7 and make sure you added the service account email as a Viewer.

"File not found" error for credentials
→ The path to your JSON key file is wrong in ga_query.py. Double-check the CREDENTIALS_PATH value. On Windows, use a raw string: r"C:\Users\..."

"API not enabled" error
→ The Google Analytics Data API isn't turned on. Go back to Step 3.

Claude Code doesn't use the skill
→ Try invoking it directly with /google-analytics. If that doesn't work, add this line to your global CLAUDE.md file (at ~/.claude/CLAUDE.md): "When asked about analytics or website traffic, use the google-analytics skill."

"Property not found" error
→ Double-check your Property ID. It should be just the number (like 363186564), not the full "properties/363186564" string.


Important Notes

  • This skill only works in local Claude Code (VS Code extension or terminal CLI). It does not work in the browser-based Claude Code on claude.ai, because that runs in a cloud sandbox without access to your local files.
  • The service account has read-only access — it cannot modify your Google Analytics settings.
  • Keep your credentials JSON file safe. Don't share it or commit it to a public code repository.
  • The pip install only needs to be done once. The package stays installed on your computer.

Top comments (0)