---
title: "Providers"
description: "GA4, Plausible, Umami, Matomo and Fathom setup, the capability matrix and caveats."
url: https://docs.systhema.app/next/payload/analytics/providers
version: unreleased (main)
docs_index: https://docs.systhema.app/next/llms.txt
---

The dashboard reads from an analytics provider you already run. This page lists what each provider can serve, how to connect it, and where providers differ.

## Provider capability matrix

Not every provider can serve every breakdown property or hourly buckets — the endpoints, the breakdown widget's property select, the Devices/Locations tabs, and the preset list are all filtered against this single matrix automatically. (A [custom provider](https://docs.systhema.app/next/payload/analytics/custom-provider.md) declares its own capability set instead, and is filtered the same way.)

| Property                         | GA4 | Plausible | Umami | Matomo | Fathom |
| -------------------------------- | :-: | :-------: | :---: | :----: | :----: |
| Pages                            |  ✓  |     ✓     |   ✓   |   ✓    |   ✓    |
| Sources                          |  ✓  |     ✓     |   ✓   |   ✓    |   ✓    |
| Countries                        |  ✓  |     ✓     |   ✓   |   ✓    |   ✓    |
| Regions                          |  ✓  |     ✓     |   ✓   |   ✓    |   ✓    |
| Cities                           |  ✓  |     ✓     |   ✓   |   ✓    |   ✓    |
| Device types                     |  ✓  |     ✓     |   ✓   |   ✓    |   ✓    |
| Browsers                         |  ✓  |     ✓     |   ✓   |   ✓    |   ✓    |
| Operating systems                |  ✓  |     ✓     |   ✓   |   ✓    |   ✓    |
| Languages                        |  ✓  |     —     |   ✓   |   ✓    |   —    |
| Age brackets                     |  ✓  |     —     |   —   |   —    |   —    |
| Gender                           |  ✓  |     —     |   —   |   —    |   —    |
| Hourly buckets (Today/Yesterday) |  ✓  |     ✓     |   ✓   |   —    |   ✓    |

GA4 is the only provider that can serve the full vocabulary — it's the recommended provider when age/gender or language breakdowns matter to you. See [Provider caveats](#provider-caveats) below for the real-world gotchas behind several of these ✓s (GeoIP requirements, privacy thresholds, launch-date gaps).

## Provider setup guides

### Google Analytics 4

GA4 uses a Google Cloud **service account** — a one-time setup:

1. In the [Google Cloud console](https://console.cloud.google.com/), create (or pick) a project and **enable the Google Analytics Data API** (APIs & Services → Library → "Google Analytics Data API").
2. Create a **service account** (IAM & Admin → Service Accounts) — no project roles needed — then open it and create a **JSON key** (Keys → Add key → JSON). The downloaded file contains `client_email` and `private_key`.
3. In Google Analytics (Admin → Property → **Property access management**), add the service account's email address with the **Viewer** role — this is what actually grants it access to the property's data.
4. `propertyId` is the numeric property id from Admin → Property settings (not the `G-…` measurement id).

> [!IMPORTANT]
> **Private-key newline gotcha:** the JSON key file stores the PEM with literal `\n` escape sequences. When you paste it into an `.env` file, **keep those `\n` sequences as-is** (one line, quoted) — Systhema normalizes them to real newlines at resolve time. A key pasted with real line breaks into a single-line env var is the most common cause of a silent GA4 signing failure.

> [!NOTE]
> **Age and gender need Google Signals.** The Age and Gender widgets only populate once [Google Signals](https://support.google.com/analytics/answer/9445345) is enabled on the property (or IDFA collection on iOS apps) — without it, expect both widgets to stay mostly empty. Even with Signals on, GA4's privacy thresholds can silently withhold individual rows below a minimum user count, which is why the Age widget carries a "may be limited by Google privacy thresholds" footnote.

No Google SDK is involved — the adapter signs its own service-account JWT with Node's built-in crypto and calls the Data API's REST endpoints directly.

### Plausible

1. In Plausible, go to your account settings → **API keys** and create a **Stats API** key.
2. `siteId` is your site's domain exactly as configured in Plausible (e.g. `example.com`).
3. Self-hosted: set `host` to your instance's base URL (e.g. `https://plausible.example.com`). The adapter uses the **Stats API v2** — Plausible Community Edition needs **v2.1.2 or newer** (the first CE release with the v2 query endpoint). The hosted plausible.io always works.

### Umami

- **Umami Cloud**: create an API key under your account settings and pass it as `apiKey` — that's the whole setup. `websiteId` is the website's UUID from the dashboard.
- **Self-hosted**: pass `host` plus `username` and `password` instead. The adapter logs in once (`POST /api/auth/login`) and reuses the returned bearer token — the token doesn't expire, but Umami v3 invalidates it when the account password changes, so the adapter transparently re-logs-in on a 401. Prefer a dedicated read-only viewer account over your admin login.
- Both Umami v2 and v3 are supported — the adapter detects the response-shape differences between them automatically.
- **Geo breakdowns need GeoIP configured.** Country/region/city breakdowns resolve from either a manually-provisioned MaxMind GeoLite2 database (self-hosted, `GEOLITE_DB_PATH`) or a supported reverse-proxy geo-header set (Cloudflare, Vercel, CloudFront, EdgeOne). Umami Cloud has this out of the box; a self-hosted instance with neither configured returns empty geo breakdowns, not an error.

### Matomo

1. In Matomo, go to your user settings → **Security** → **Auth tokens** and create a token (`token_auth`).
2. `host` is your instance's base URL, `siteId` the numeric `idSite`.
3. Newly created Matomo tokens are **POST-only by default** — the adapter always sends `token_auth` in the POST body, never in a URL, so tokens don't leak into server logs. You don't need to enable "allow this token in GET requests".

### Fathom

1. In Fathom, go to Settings → **API** and create an API key.
2. `siteId` is the site's id (e.g. `ABCDEFGH`).

> [!WARNING]
> **Quota note:** Fathom counts **every API request against your plan's pageview quota** (free tier: 600 requests/hour), and that now includes the Devices, Locations, Age/Gender-adjacent breakdown calls and the hourly Today/Yesterday timeseries — a busier dashboard multiplies call volume more than it did with the original four widgets. Systhema's server-side cache and request deduplication keep the call volume low, but don't disable the cache on a Fathom site.

## Provider caveats

The adapters normalize every provider into one canonical vocabulary, but the providers genuinely differ in a few places. These differences are documented, not papered over:

| Caveat                                          | Detail                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Chart shows Visitors + Views only               | The per-bucket timeseries carries `pageviews` + `visitors` — the cross-provider common denominator (Umami has no per-bucket bounce/duration series at all). Sessions, bounce rate and average duration appear as aggregate tiles with deltas instead.                                                                                                                                                                          |
| Matomo has no Today/Yesterday presets           | Matomo's only hourly report (`VisitTime`) is an hour-of-day histogram (24 buckets, 00–23) — a multi-day range folds into the same 24 buckets rather than producing a chronological timeline — and it has no pageviews-only metric (its `nb_actions` counts every interaction type, not just pageviews). With no true hourly pageviews timeseries, Matomo sites don't offer the Today/Yesterday presets at all.                 |
| Timezone semantics vary                         | Preset boundaries are computed in the dashboard **viewer's** browser timezone; each provider then resolves the resulting calendar dates in **its own** timezone — GA4 in the property's reporting timezone, Plausible/Matomo/Fathom in the site's configured timezone, Umami in UTC. Bucket boundaries can differ by up to a day near midnight when these disagree. See [Period selection](https://docs.systhema.app/next/payload/analytics.md#period-selection) above. |
| Matomo range visitors are approximate           | Matomo doesn't compute unique visitors for multi-day ranges by default (`nb_uniq_visitors` is absent for `period=range` on Cloud and stock self-hosted) — the aggregate visitor count falls back to **summing the daily uniques**, which is not deduplicated across days (a visitor active on three days counts three times).                                                                                                  |
| Plausible page breakdowns rank by pageviews     | Plausible's v2 API can't mix session metrics with the page dimension — page breakdowns are ranked by views/visitors only (which is what the breakdown widget shows anyway).                                                                                                                                                                                                                                                    |
| GA4 age/gender need Google Signals              | Age brackets and gender stay mostly `(not set)` until Google Signals is enabled on the property (or IDFA collection on iOS apps), and rows below GA4's privacy-threshold minimum are silently withheld regardless. Sparse or missing rows are expected, not a bug — GA4 is the only provider that can populate these widgets at all.                                                                                           |
| Umami geo breakdowns need GeoIP                 | Self-hosted Umami needs either a provisioned GeoLite2 database or a supported reverse-proxy geo header set to resolve country/region/city — without it, every geo breakdown returns empty rows, not an error. Region values are always the raw ISO-3166-2 code (e.g. `US-CA`) — Umami has no name lookup for subdivisions, so the widget shows the code as-is.                                                                 |
| Umami Cloud clamps long ranges                  | A non-team, no-subscription Umami Cloud website silently floor-clamps any range — a "Last 12 months" preset or a wide custom range — to roughly six months back; older data is dropped from the response with no error.                                                                                                                                                                                                        |
| Fathom's newer breakdowns have launch-date gaps | Operating system, region and city data exist only from 2025-06-17, 2025-06-26 and 2025-07-03 respectively (a few days later for EU visitors) — a range entirely before those dates returns empty rows for that dimension even though pageviews/visitors are populated normally for the same period.                                                                                                                            |
| Fathom previous periods cost a second call      | Fathom has no built-in comparison — the aggregate (and the chart's compare overlay) makes two sequential API calls, current then previous window.                                                                                                                                                                                                                                                                              |
| Fathom has no sessions metric                   | Fathom's visitor count comes from its site-level `visits` aggregate ("unique site visits" — its dashboard's Site Visitors number); the per-page `uniques` aggregate would overstate visitors and isn't used. Fathom offers nothing session-shaped, so the Sessions tile shows an em-dash on Fathom sites.                                                                                                                      |
| Fathom's quota adds up fast                     | Every API request — including the new breakdown, hourly and comparison calls — counts against Fathom's pageview quota (free tier: 600 requests/hour). The extra widgets meaningfully multiply call volume per dashboard load; the `payload.kv` response cache is what keeps this in check, so don't disable it on a Fathom site.                                                                                               |
| Live windows differ                             | "Live" means the provider's own realtime window: 5 minutes for Plausible/Umami/Matomo (and assumed for Fathom, which doesn't document it), **30 minutes for GA4** (60 for GA360). The live widget's caption names the actual window.                                                                                                                                                                                           |

> [!NOTE]
> **A note on Payload's dashboard API:** the modular dashboard (`admin.dashboard`) is marked **experimental** in Payload and may change between releases. Systhema's widget registration is defensive and isolated, and the required Payload version is enforced by the package's `^3.85.0` peer range (the first release with dashboard widgets) — no runtime version sniffing.
