---
title: "Custom provider"
description: "Bring your own analytics backend."
requested_language: fr
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/fr/payload/analytics/custom-provider
version: unreleased (main)
docs_index: https://docs.systhema.app/fr/llms.txt
---
> This page isn't translated yet. Showing English.


`source: 'custom'` plugs any data source into the full dashboard experience — an internal data warehouse, an analytics product Systhema doesn't ship an adapter for, or a proxy in front of one that it does. You implement the same four data methods the built-in adapters do; the module supplies everything around them (range resolution, `payload.kv` caching with stale-serving, capability gating, widget seeding, the settings endpoint, access control).

## Implementing an adapter

```ts title="payload.config.ts"
import type { CustomAnalyticsAdapter } from '@systhemaui/payload'

const warehouse: CustomAnalyticsAdapter = {
  label: 'Internal warehouse', // shown where a built-in provider's name would be
  dashboardUrl: 'https://warehouse.example.com/analytics', // optional — powers the widgets' external-link icon
  capabilities: {
    breakdowns: ['page', 'source', 'country', 'device', 'browser', 'os'],
    hourly: false, // drops the Today/Yesterday presets, exactly like Matomo
  },
  liveWindowMinutes: 5, // the "last N min" caption on the live widget; default 30
  async timeseries({ range, compare }) {
    // range is fully resolved: { from, to, bucket, previous, tz } with
    // inclusive ISO dates — return zero-filled chronological points
    // (plus previousPoints when compare is true).
    return { bucket: range.bucket, points: [] }
  },
  async aggregate({ range }) {
    // All five metrics; use null for a metric you cannot supply.
    return {
      current: { pageviews: 0, visitors: 0, sessions: null, bounceRate: null, avgDuration: null },
      previous: null,
    }
  },
  async breakdown({ range, property, limit }) {
    return { property, metric: 'visitors', rows: [] }
  },
  async live() {
    return { visitors: 0 }
  },
}

export default withSysthema(buildConfig({ /* … */ }), {
  analytics: { provider: { source: 'custom', adapter: warehouse } },
})
```

## Notes

- **Capabilities are per-instance.** Built-in providers use the static capability matrix below; a custom adapter declares its own `capabilities`, and every consumer of the matrix (endpoint validation, the settings vocabularies, the breakdown drawer options, default widget seeding — including the otherwise GA4-only age/gender widgets) honors the declared set. Omitted fields are permissive: all eleven breakdown properties, hourly supported.
- **`dashboardUrl` is optional.** Declare it to power the "Provided by \<label\>" attribution's external-link icon on every widget (opens your backend's own dashboard in a new tab); omit it and the icon simply doesn't render.
- **Methods may be sync or async** — return the value or a promise.
- **Throw `AnalyticsProviderError`** (exported from `@systhemaui/payload`) when your backend fails, so the cache layer can serve stale data and the endpoints answer with an opaque 502; any other thrown error is treated the same way defensively.
- **Caching applies as usual** under the `custom:` cache-key scope; tune or disable it via the `cache` option like any other provider.
