Analytics
Django-CFG ships first-party analytics: pageviews, cookieless sessions, and — the thing hosted analytics structurally cannot give you — attribution to your signed-in users.
It is on by default and needs no configuration. The admin tab appears as soon as migrations are applied and renders zero-filled metrics until the first event registers a site. Nothing else runs.
Why it exists
Hosted analytics (Google Analytics, Plausible, Umami Cloud) can tell you that someone visited /pricing. They cannot tell you it was the user on your Pro plan who opened a support ticket yesterday — they do not have your user table.
Django-CFG does. AnalyticsEvent.user is a real FK to AUTH_USER_MODEL.
Features
- Zero configuration — enabled by default; the site row is created automatically
- Zero extra processes — no Celery, no RQ worker, no daemon, no cron, no sidecar
- Automatic user attribution — every successful framework login is linked to its authenticated user, even when browser beacons cannot carry a JWT
- Host properties — related trusted hosts are grouped automatically, with both property and single-host reports
- Typed product events — custom events flow through one client API; configure Django goals and ordered funnels by event name
- Server-confirmed events — record trusted backend facts (webhooks, jobs) exactly once from Python, excluded from traffic metrics
- Cookieless — no cookies, no consent banner needed for the mechanism itself
- IP and User-Agent are never stored — they are hashed away at ingest
- Bot filtering — crawlers are dropped silently
- Channel classification — search / social / AI assistants / paid / referral, resolved at write time
- Admin dashboard — a dual-series daily chart: pageviews with a numeric scale and unique visitors as a line, exact values on focus, plus compact searchable ranked lists
Quick start
There is no quick start. It is already running.
from django_cfg import DjangoConfig
class MyConfig(DjangoConfig):
project_name = "My Project"
security_domains = ["example.com"] # this is all analytics needsRun migrations to make the Analytics tab available. The first pageview from example.com then registers the site and starts collecting.
To send events from a Next.js or React frontend, see Frontend.
Architecture
The ingest is synchronous. The event is written in the request thread and the endpoint answers 202. There is no queue and no worker.
That is not a shortcut — it is the production baseline. Measured on durable storage:
| Operation | Time |
|---|---|
| INSERT + COMMIT | 1.895 ms |
same, with synchronous_commit = off | 0.098 ms |
| opening a database connection | 2.796 ms |
Opening the connection costs more than the durable write it carries. Any design that adds a queue, a file, or a drain daemon to defer a 0.1 ms write is optimizing the wrong thing. (Umami, the most widely deployed self-hosted analytics on PostgreSQL, writes synchronously and ships no worker at all.)
What gets stored
| Model | Purpose |
|---|---|
AnalyticsProperty | A logical product, normally the trusted apex domain; it aggregates related hosts. |
AnalyticsSite | One row per domain. The host boundary and timezone anchor. |
AnalyticsSession | One row per visit. All mutable state (pageviews, duration, bounce). |
AnalyticsEvent | Append-only. One row per pageview or custom event. |
Key event columns: site, ts (UTC), visitor_id, session, user, pathname, route, locale, referrer_domain, channel, utm_*, props (JSONB), is_measurement (false for server-confirmed facts so they never inflate traffic), source_id_hash (HMAC digest backing server-event idempotency).
Reports
Aggregation happens at read time — plain GROUP BY over the event table, backed by a (site, ts, <dimension>) index prefix. No rollup tables, no materialized views, no cron job.
from django_cfg.apps.tools.analytics.models import AnalyticsProperty, AnalyticsSite
from django_cfg.apps.tools.analytics.services import Period, reports
site = AnalyticsSite.objects.get(domain="example.com")
property_ = AnalyticsProperty.objects.get(domain="example.com")
period = Period.last_days(7)
reports.summary(site, period) # visitors, pageviews, sessions, bounce_rate, known_users
reports.timeseries(site, period) # per site-local day, gaps included as zeros
reports.top_pages(site, period) # grouped by templated route
reports.top_referrers(site, period)
reports.breakdown(site, period, "channel") # or browser / os / device / country / language
reports.online_now(site) # distinct visitors in the last 5 minutes
reports.user_journey(site, user_id) # every hit by one authenticated user
reports.summary(property_, period) # all trusted hosts; users are de-duplicatedAt property scope, pageviews, visitors, and sessions remain host-scoped totals;
only authenticated users are safely de-duplicated with DISTINCT user_id.
reports.user_journey() is the one no hosted analytics product can offer you.
Goals and funnels
Create an Analytics goal in Django Admin by giving it a name, an event name, and either one host or one property. Create an Analytics funnel in the same place, then add ordered steps with the event names emitted by your application.
The dashboard counts goals by distinct visitor and funnels only when a visitor
reaches steps in order during the selected period. Visitor identities remain
host-scoped at property level; Django does not pretend an anonymous visitor on
two subdomains is the same person. For frontend events and the typed catalog,
see the @djangocfg/analytics package README.
Read next
- Configuration — every setting and what it costs
- Frontend —
@djangocfg/analyticsfor Next.js and React - Server events — record trusted backend facts exactly once from Python
- Privacy — what is collected, what is not, and GDPR