Appearance
ga4
The ga4 plugin reports Google Analytics 4 figures for every account and property one Google login can see. Per property it sends users active right now, users, sessions and page views for today, yesterday and the last 7 days, and 30 days of daily figures. It answers "is traffic still arriving", and it pages when Google stops answering.
Set it up
Google Analytics has no service-account path for this plugin. It runs on a user refresh token with the analytics.readonly scope, minted once by hand.
In Google Cloud, create an OAuth client of type Desktop app and download its JSON.
Enable the Google Analytics Admin API and the Google Analytics Data API in that project.
Set the OAuth consent screen to In production, or Internal. A client left in Testing hands out refresh tokens that expire after 7 days.
From the
clientdirectory, run the token helper.bashnode etc/google-auth.mjs --source=ga4 <client_secret.json> [ssh-host] [remote-config-path]It opens Google's consent page, catches the redirect on a loopback port and trades the code for a refresh token. Before it saves anything, it checks that the token can see at least one Analytics account. With no
ssh-hostit writes thega4block into./client.config.json. With one, it writes into that host's config over ssh and sends the secrets on stdin. SetBROWSER_APPto pick the browser that holds the right Google login.Add
ga4to the agent'spluginsarray and restart the agent.
The Google account you consent with must have access to the Analytics properties.
json
{
"plugins": ["ga4"],
"ga4": {
"name": "Google Analytics",
"uniqueId": "ga4",
"refreshInterval": 900000,
"clientId": "<CLIENT_ID>.apps.googleusercontent.com",
"clientSecret": "<CLIENT_SECRET>",
"refreshToken": "<REFRESH_TOKEN>",
"accounts": ["<GA_ACCOUNT_ID>"]
}
}| Option | Default | What it does |
|---|---|---|
clientId | required | OAuth client id. |
clientSecret | required | OAuth client secret. |
refreshToken | required | Refresh token from google-auth.mjs. |
accounts | every account | GA account ids to collect, as strings. Accounts outside the list are never queried. |
refreshInterval | 900000 | Milliseconds between reports. |
timeout | 20000 | Milliseconds per HTTP request. |
Set accounts unless the login sees only this customer's accounts. A Google login often sees accounts that belong to other customers, and everything collected lands in the view of the system this agent reports to.
client.config.json.example has no ga4 block. google-auth.mjs creates one.
What it reports
One report per interval, sent immediately on start.
name, alwaysGoogle Analytics.properties, the number of properties reported.accounts[], each withid,nameandproperties[].- Per property,
id,name,asOf, which is today's date in the property's time zone, andactiveNow, the users active in the last 30 minutes.activeNowis null when the realtime call fails. - Per property,
today,yesterdayandlast7, each withusers,sessionsandviews. The 7-day users figure comes from GA itself, so one person on three days counts once. - Per property,
dailywith astartdate and 30-entryusers,sessionsandviewsarrays. Days with no traffic are filled with 0. - A property that fails carries
errorin place of figures. The others still report. - When the whole poll fails, the report is
{ error }, plusauthExpired: trueif Google answeredinvalid_grant.
Only ordinary GA4 properties are listed. The plugin filters out other property types.
The server does not write a history row per report. The type is in SELF_HISTORIED, because each report already carries 30 days.
When it alerts
| Condition | Severity | Reason text |
|---|---|---|
authExpired | error | Google Analytics: authorization expired, mint a new refresh token |
error | error | Google Analytics: <error> |
clientId, clientSecret or refreshToken missing | error | Google Analytics: ga4 plugin needs clientId, clientSecret and refreshToken |
One or more properties carry error | warning | Google Analytics: <n> properties failed to report (<first name>: <error>) |
A new problem does not notify on the first bad report. The server waits for a second consecutive bad report, which is 15 minutes later at the default interval. A report that comes back clean resolves the alert.
There is no traffic threshold. A property that drops to zero users does not alert.
On the dashboard
The card is titled Google Analytics.
| Reading | Source |
|---|---|
| PROPERTIES | Number of properties |
| ACTIVE NOW | Sum of activeNow across properties |
| USERS TODAY | Sum of today.users |
| SESSIONS 7D | Sum of last7.sessions |
Below that, one row per property with today's users, or error in amber. While the report carries a top-level error, the figures blank out rather than showing zeroes. The card links to Google Analytics.
The card goes OFFLINE after 1800 seconds without a report, which is two missed polls at the default interval.
Gotchas
- A failed realtime call is silent.
activeNowbecomes null, and ACTIVE NOW counts it as 0. - Changing
accountsis a privacy decision, not a display one. Hiding properties and nicknames live in the portal. - Refresh tokens from a consent screen in Testing die after 7 days, and the card turns red with
authorization expired. Rungoogle-auth.mjsagain with the app set to In production. - If Google returns no refresh token, remove the app's access at myaccount.google.com/permissions and run the helper again.