September: sessions up 7%, key events up 9%
September brought 118,400 sessions (+7% on August, +31% on September 2025) and 4,620 key events (+9%). Organic Search carried the growth; Paid Search stayed flat after the budget cut on the 12th.
On the 2nd of each month: last month's GA4 traffic, users and key events against the month before and the same month last year, in plain words.
Reads
You get
The card on the right is a sample. Yours shows your own numbers and updates in place every run.
What lands on your board
September brought 118,400 sessions (+7% on August, +31% on September 2025) and 4,620 key events (+9%). Organic Search carried the growth; Paid Search stayed flat after the budget cut on the 12th.
Agencies and in-house marketers spend the first morning of each month rebuilding the same report: pick the dates, set the comparison, screenshot the channel chart, and write three sentences a client or a board member will actually read. The numbers are quick. The sentences are what eat the morning.
The agent drafts both. It compares the month with the one before and with the same month last year, so a seasonal dip is not mistaken for a problem, and it writes the summary in words that someone outside marketing can follow.
Call runReport with three dateRanges: last calendar month, the month before, and the same month a year earlier. Metrics: sessions, activeUsers, newUsers, engagementRate, keyEvents and, for a store, totalRevenue.
Run the same ranges by sessionDefaultChannelGroup, then the top 10 by landingPagePlusQueryString, then the top 5 by deviceCategory and by country, so each section of the report has its own small table.
Look at daily sessions (dimension date) for the month before writing anything. A run of days near zero means the tag broke, and the report should say so instead of explaining a drop that never happened.
Three to five sentences up top: what happened, why, and what comes next. Then the tables. Give changes in percent, round large numbers, and give engagement rate changes in percentage points.
With BRIEFKIN_TOKEN set, the agent sends the result as one card that updates in place every run. Without it, you get the report in the chat.
Pick your agent, where the data comes from and how to schedule it; the commands below follow.
One command. It puts the SKILL.md in ~/.claude/skills and in ~/.agents/skills, which Codex, Cursor, Gemini CLI, Copilot CLI and OpenCode read too.
shellcurl -fsSL https://briefkin.dev/skills/google-analytics-monthly-report/install | shAsk Claude Code:
promptRun the google-analytics-monthly-report skillOr paste the prompt instead of installing:
promptWrite my monthly Google Analytics 4 report for last month. Using the Analytics Data API, compare last calendar month with the month before and with the same month last year: sessions, activeUsers, newUsers, engagementRate and keyEvents (and totalRevenue if the property has ecommerce). Add sessions by sessionDefaultChannelGroup, the top 10 landing pages, and the top devices and countries. Check daily sessions for tracking gaps first and mention any. Open with three to five plain sentences for a reader outside marketing: what happened, the likely reason, what we do next. Give engagement rate changes in percentage points.2nd of the month at 9:00, from cron on any machine where Claude Code is signed in:
crontab0 9 2 * * claude -p "Run the google-analytics-monthly-report skill"Add it with crontab -e. A laptop asleep at that time runs it when it wakes if you use launchd or Task Scheduler instead; GitHub Actions works for the API route.
Connect Briefkin once with curl -fsSL https://briefkin.dev/install | sh and every run lands on your board as a report card. How setup works
SKILL.md---
name: google-analytics-monthly-report
description: "On the 2nd of each month: last month's GA4 traffic, users and key events against the month before and the same month last year, in plain words. Use when the user asks for this, or when it runs on a schedule."
---
# GA4 monthly traffic report
On the 2nd of each month: last month's GA4 traffic, users and key events against the month before and the same month last year, in plain words.
Agencies and in-house marketers spend the first morning of each month rebuilding the same report: pick the dates, set the comparison, screenshot the channel chart, and write three sentences a client or a board member will actually read. The numbers are quick. The sentences are what eat the morning.
The agent drafts both. It compares the month with the one before and with the same month last year, so a seasonal dip is not mistaken for a problem, and it writes the summary in words that someone outside marketing can follow.
## Two ways to get the data
Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.
## Route A: API or MCP server
### What you need
- The Viewer role on each GA4 property you report on; agencies can list several property IDs, one card per client.
- Data API access for the agent, through an Analytics MCP server, a connector, or a service account added to every property in the list.
If something is missing, say what and stop; do not invent numbers.
### Steps
1. **Pull the month and its comparisons** (Google Analytics): Call `runReport` with three `dateRanges`: last calendar month, the month before, and the same month a year earlier. Metrics: `sessions`, `activeUsers`, `newUsers`, `engagementRate`, `keyEvents` and, for a store, `totalRevenue`.
2. **Break it down** (Google Analytics): Run the same ranges by `sessionDefaultChannelGroup`, then the top 10 by `landingPagePlusQueryString`, then the top 5 by `deviceCategory` and by `country`, so each section of the report has its own small table.
3. **Check for tracking gaps first** (Google Analytics): Look at daily `sessions` (dimension `date`) for the month before writing anything. A run of days near zero means the tag broke, and the report should say so instead of explaining a drop that never happened.
4. **Write it for a reader outside marketing**: Three to five sentences up top: what happened, why, and what comes next. Then the tables. Give changes in percent, round large numbers, and give engagement rate changes in percentage points.
## Route B: the browser
### What you need
- Signed in to Google with the Viewer role on each GA4 property you report on; an agency account switches property between clients with the picker at the top left.
If something is missing, say what and stop; do not invent numbers.
### Steps
Start at https://analytics.google.com/analytics/web/. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.
1. **Check for tracking gaps first**: Pick the property, open Reports > Acquisition > Traffic acquisition, set the date range to `Last month` and look at the chart with Day granularity. A run of days near zero means the tag broke; the report should say so before it explains anything.
2. **Read the headline against last month**: Open Reports > Reports snapshot with `Last month`, switch on Compare with `Previous period (match day of week)` and Apply. Read Active users, New users, Sessions, Engagement rate, Key events and, for a store, Total revenue with their change.
3. **Repeat against last year**: Change the comparison to `Previous year` and read the same numbers again. Keep both sets: a dip against last month that also happened last year is seasonal.
4. **Collect the breakdowns**: With the same dates, read Sessions by `Session default channel group` in Traffic acquisition, the top 10 rows in Reports > Engagement > Landing page, the top 5 in Reports > Tech > Tech details (`Device category`) and in Reports > User > User attributes > Demographic details (`Country`).
5. **Write for a reader outside marketing**: Three to five sentences up top: what happened, why, what comes next. Then the tables. Changes in percent, large numbers rounded, engagement rate changes in percentage points.
Note: Run it on the 2nd or later so the last day of the month is processed. For several clients, repeat the same steps per property; the comparisons are the ones the snapshot already draws, so the browser route works well for one or two properties and gets long beyond that.
## The report
- **Summary**: three to five sentences on the month for a reader who never opens GA4.
- **Headline table**: sessions, active users, new users, engagement rate and key events, against last month and last year.
- **Channels**: sessions by default channel group.
- **Top landing pages**, **devices** and **countries**, top entries only.
- **Data notes**: tracking gaps or settings changes that affect the numbers.
## Post it to Briefkin
When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.
```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d @- <<'JSON'
{
"v": 1,
"event_id": "google-analytics-monthly-report/<date of this run, e.g. 2026-10-16>",
"item_key": "google-analytics-monthly-report",
"project": "example.com",
"source": "GA4 monthly traffic report skill",
"kind": "update",
"title": "September: sessions up 7%, key events up 9%",
"summary": "September brought **118,400 sessions** (+7% on August, +31% on September 2025) and **4,620 key events** (+9%). Organic Search carried the growth; Paid Search stayed flat after the budget cut on the 12th.",
"widget": [
{
"type": "Table",
"props": {
"columns": [
"Metric",
"September",
"vs August",
"vs Sep 2025"
],
"align": [
"left",
"right",
"right",
"right"
],
"rows": [
[
"Sessions",
118400,
"+7%",
"+31%"
],
[
"Active users",
86900,
"+6%",
"+28%"
],
[
"New users",
64200,
"+5%",
"+24%"
],
[
"Engagement rate",
"60.8%",
"+1.2 pts",
"+3.5 pts"
],
[
"Key events",
4620,
"+9%",
"+44%"
]
]
}
},
{
"type": "Text",
"props": {
"text": "Sessions by channel",
"variant": "heading"
}
},
{
"type": "Bars",
"props": {
"items": [
{
"label": "Organic Search",
"value": 51300
},
{
"label": "Direct",
"value": 25900
},
{
"label": "Paid Search",
"value": 17800
},
{
"label": "Referral",
"value": 9400
},
{
"label": "Email",
"value": 7600
},
{
"label": "Organic Social",
"value": 6400
}
]
}
}
]
}
JSON
```
Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.
## Run it on a schedule
Suggested: 2nd of the month at 9:00 (`0 9 2 * *`). From cron, with the agent signed in on that machine:
```bash
0 9 2 * * claude -p "Run the google-analytics-monthly-report skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```
Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/google-analytics-monthly-report#schedule
Source: https://briefkin.dev/skills/google-analytics-monthly-report
The Viewer role on each GA4 property you report on; agencies can list several property IDs, one card per client.
Data API access for the agent, through an Analytics MCP server, a connector, or a service account added to every property in the list.
analytics-mcp on PyPI, source). It runs locally with pipx run analytics-mcp, offers run_report, run_realtime_report and get_account_summaries, and Google labels it experimental.GOOGLE_APPLICATION_CREDENTIALS at its JSON key. The skill also reads GA4_PROPERTY_ID.gcloud auth application-default login --scopes https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform --client-id-file=client.json gives the MCP server or a script your user's access without a key file.Request only analytics.readonly and keep the service account at Viewer: none of these reports needs Editor.
Yes. List the property IDs with a client name for each. The agent runs the same report per property and posts one card per client, with the client name in the item_key so each keeps its own history.
GA4 can take a day to finish processing, so on the morning of the 1st the last day of the month is often incomplete. Waiting one day keeps the month whole and the comparison with last year fair.
No. The skill runs with your own agent and gives you the report in the chat. With a Briefkin token set, each run also posts it to your board as a report card, so you can glance at it instead of asking again.
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot CLI, OpenCode, Windsurf and Amp all read SKILL.md files. The install command puts it in ~/.claude/skills and ~/.agents/skills, where they look. ChatGPT, the Claude app and Grok take the prompt instead. Pick your agent under "Install and run it" and the commands change to match.
Yes. Switch the data source to "Your browser" and the steps change to the Google Analytics pages themselves: the agent opens analytics.google.com/analytics/web in a browser you are signed in to and reads the numbers off the page. Claude Code, Codex, Cursor, Claude app and ChatGPT can drive a browser out of the box; Gemini CLI, Copilot CLI and OpenCode need a Playwright or Chrome DevTools MCP server first. It is slower than the API and needs you signed in, so it suits a report you run by hand or from a scheduler on your own computer.
Skills are free to use with any agent. Briefkin is where their results land: one board for every report, number and question your agents send, with a ping when something needs you.
Free during the open beta. No card.