---
title: "Providers"
description: "GA4, Plausible, Umami, Matomo and Fathom setup, the capability matrix and caveats."
requested_language: nl
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/nl/next/payload/analytics/providers
version: unreleased (main)
docs_index: https://docs.systhema.app/nl/next/llms.txt
---
> This page isn't translated yet. Showing English.


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/nl/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/nl/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.
