ga4-query
GA4 Query
Setup
Check prerequisites before querying:
Script path resolution: Before running any script, determine the correct base path. Check in this order:
.claude/skills/ga4-query/scripts/— project-local install (run from repository root)~/.claude/skills/ga4-query/scripts/— global install
Use whichever path exists. All script paths below use the project-local form; substitute the global path if that's where scripts are found.
- Virtual environment:
.claude/skills/ga4-query/scripts/.venv/must exist - Config file:
ga4-config.yamlmust be findable (see Config section) - APIs enabled in GCP project: Google Analytics Data API + Google Analytics Admin API
First-time setup
# 1. Install dependencies
bash .claude/skills/ga4-query/scripts/setup.sh
# 2. Create ga4-config.yaml in project root or ~/.config/ga4-query/
# (see Config section for template)
# 3. Enable APIs in GCP Console:
# - Google Analytics Data API (analyticsdata.googleapis.com)
# - Google Analytics Admin API (analyticsadmin.googleapis.com)
# 4. Test
ga4 accounts
Claude Code sandbox
When running inside the Claude Code sandbox, network access is restricted. Add the following domains to ~/.claude/settings.json under sandbox.network.allowedDomains:
Setup (pip install):
pypi.orgfiles.pythonhosted.org
Runtime (GA4 API):
analyticsdata.googleapis.comanalyticsadmin.googleapis.comoauth2.googleapis.com
Config
The script searches for ga4-config.yaml in this order:
--configCLI parameter (explicit path)./ga4-config.yaml(project root)~/.config/ga4-query/ga4-config.yaml(user-level)
Template (ga4-config.yaml):
property_id: "YOUR_PROPERTY_ID"
credentials_json: "/path/to/service-account.json"
The service account must have Viewer role on the GA4 property. Auth uses service_account.Credentials.from_service_account_file() with scope analytics.readonly.
Usage
Use the ga4 alias:
alias ga4='.claude/skills/ga4-query/scripts/.venv/bin/python3 .claude/skills/ga4-query/scripts/query.py'
Subcommands
report — Standard reports (main command)
# Sessions and users by date (last 7 days)
ga4 report -d date -m sessions,activeUsers --start 7daysAgo --end yesterday
# Top pages by views
ga4 report -d pagePath -m screenPageViews --start 30daysAgo --end yesterday --order-by screenPageViews --desc --limit 20
# Traffic sources
ga4 report -d sessionSource,sessionMedium -m sessions --start 30daysAgo --end yesterday
# With filter
ga4 report -d pagePath -m screenPageViews --start 7daysAgo --end yesterday --filter "pagePath=~/palvelut"
# Multiple filters (ANDed)
ga4 report -d date -m sessions --start 7daysAgo --end yesterday --filter "country==Finland" --filter "sessionSource==google"
# JSON via stdin (recommended — avoids shell escaping issues)
echo '{"dimensions":["pagePath"],"metrics":["screenPageViews"],"dateRanges":[{"startDate":"7daysAgo","endDate":"yesterday"}]}' | ga4 report --json -
# JSON as direct argument (only for simple JSON with no nested quotes)
ga4 report --json '{"dimensions":["pagePath"],"metrics":["screenPageViews"],"dateRanges":[{"startDate":"7daysAgo","endDate":"yesterday"}]}'
Flags:
-d/--dimensions— Comma-separated dimension names-m/--metrics— Comma-separated metric names (required unless--json)--start— Start date (default:7daysAgo)--end— End date (default:yesterday)--filter— Dimension filter (repeatable, ANDed together)--order-by— Sort field name--desc— Descending order (use with--order-by)--limit— Max rows--json— Raw JSON request body (overrides all other flags)
realtime — Live data
ga4 realtime -m activeUsers
ga4 realtime -d country -m activeUsers
No date range — realtime always returns last ~30 minutes.
Admin commands
ga4 accounts # List accounts + properties
ga4 property # Current property details
ga4 custom-dims # Custom dimensions
ga4 custom-metrics # Custom metrics
Global flags
--config /path/to/ga4-config.yaml # Override config search
--property 123456789 # Override property_id from config
Filter Syntax
| Syntax | Match type | Example |
|---|---|---|
field==value |
EXACT | country==Finland |
field!=value |
NOT EXACT | pagePath!=/ |
field=~value |
CONTAINS | pagePath=~/palvelut |
field=^value |
BEGINS_WITH | pagePath=^/blog |
field=$value |
ENDS_WITH | pagePath=$.html |
field=*v1,v2 |
IN_LIST | country=*Finland,Sweden |
field>N |
GREATER_THAN | sessions>100 |
field>=N |
GREATER_THAN_OR_EQUAL | sessions>=50 |
field<N |
LESS_THAN | sessions<10 |
field<=N |
LESS_THAN_OR_EQUAL | sessions<=5 |
Multiple --filter flags are ANDed. For OR logic or nested NOT, use --json passthrough via stdin (echo '...' | ga4 report --json -).
Date Formats
YYYY-MM-DD— Specific date (2024-01-15)NdaysAgo— Relative (7daysAgo,30daysAgo)yesterday— Yesterdaytoday— Today (partial data)
Common Dimensions & Metrics
See references/dimensions.md and references/metrics.md for curated lists.
Quick reference — Dimensions
| Dimension | Description |
|---|---|
date |
Date (YYYYMMDD format) |
pagePath |
Page URL path |
pageTitle |
Page title |
sessionSource |
Traffic source |
sessionMedium |
Traffic medium |
sessionCampaignName |
Campaign name |
country |
User country |
city |
User city |
deviceCategory |
desktop/mobile/tablet |
Quick reference — Metrics
| Metric | Description |
|---|---|
sessions |
Total sessions |
activeUsers |
Active users |
screenPageViews |
Page views |
engagementRate |
Engaged sessions / total |
averageSessionDuration |
Avg session length (seconds) |
bounceRate |
Non-engaged sessions / total |
conversions |
Key event conversions |
totalRevenue |
Total revenue |
Output Format
Success — JSON to stdout:
{
"row_count": 7,
"rows": [
{"date": "20240101", "sessions": "142", "activeUsers": "98"}
],
"metadata": {
"dimensions": ["date"],
"metrics": ["sessions", "activeUsers"]
}
}
Errors — JSON to stderr + exit code 1:
{"error": "..."}
Notes
- GA4 returns all values as strings (unlike Google Ads micros). Numeric aggregation must be done after parsing.
- Realtime data covers approximately the last 30 minutes. Not all dimensions/metrics are available in realtime.
- GA4 has quota limits: 25,000 tokens/property/day for standard properties. Avoid excessive polling.
datedimension returns formatYYYYMMDD(no dashes).row_countin response reflects total matching rows (may exceed--limit).- Admin commands (
accounts,property,custom-dims,custom-metrics) require the Admin API to be enabled separately from the Data API.