Analytics
Sovrium ships a built-in, first-party analytics engine. It tracks page views, sessions, referrers, UTM campaigns, and device breakdowns on a single unified event model — with no cookies, no fingerprinting, and no external services. All data stays on your server, which makes it GDPR-friendly by default.
Analytics is configured via the analytics property in the app schema. When enabled, Sovrium injects a lightweight (~1KB) tracking script that records page views via navigator.sendBeacon() to POST /api/analytics/collect.
Configuration
analytics accepts a boolean shorthand or an object for fine-grained control.
# Boolean shorthand — enable with all defaults
analytics: true
# Object form — override individual settings, keep defaults for the rest
analytics:
enabled: true
retentionDays: 365
excludedPaths:
- /admin/*
- /api/*
respectDoNotTrack: true
sessionTimeout: 30
| Property | Default | Range / Values | Description |
|---|---|---|---|
enabled |
true |
boolean | Enable/disable analytics tracking. |
retentionDays |
365 |
1–730 |
Days to retain analytics data. Older rows are automatically purged. |
excludedPaths |
[] |
string[] (glob) | URL patterns excluded from tracking (e.g. /admin/*, /api/*). |
respectDoNotTrack |
true |
boolean | Honor the browser DNT:1 header — those visitors are not tracked. |
sessionTimeout |
30 |
1–120 (minutes) |
Idle period after which a new session begins. |
Boolean vs object. analytics: true enables defaults (365-day retention, DNT respected, 30-minute sessions). Use the object form to override specific settings while keeping defaults for the rest. analytics: false (or omitting the property) disables analytics — no endpoints are mounted.
Unified Events Model
All analytics events — page views, custom track calls, and future sources — share a single system.analytics_events table with an event_type discriminator column. This follows the industry-standard pattern used by PostHog, Mixpanel, and Amplitude, but self-hosted with no external dependency (see DEC-019).
system.analytics_events
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
app_name TEXT NOT NULL
event_type TEXT NOT NULL -- 'page_view' | 'track' | extensible
event_name TEXT NOT NULL -- path for page_view, custom name for track
org_id TEXT -- organization scope (nullable pre-auth)
timestamp TIMESTAMPTZ NOT NULL DEFAULT now()
visitor_hash TEXT NOT NULL -- privacy-preserving visitor identifier
session_hash TEXT NOT NULL -- privacy-preserving session identifier
properties JSONB NOT NULL DEFAULT '{}'
| Event type | event_name |
properties payload |
|---|---|---|
page_view |
The page path | path, referrer, utm_*, device_type, browser_*, os_*, screen_*, language, country. |
track |
The custom event name | Arbitrary key-value metadata from an automation action or the tracking API. |
The promoted columns (event_type, event_name, org_id, visitor_hash, session_hash, timestamp) drive efficient WHERE/GROUP BY queries; everything else lives in JSONB. The same privacy primitives — server-side visitor/session hashing (SHA-256, no PII stored), DNT respect, and the retention horizon — apply uniformly to every event type, and every row is org-scoped for multi-tenant isolation.
Storage-engine parity. The unified events table is exercised by both the PostgreSQL (->> over system.analytics_events) and SQLite (json_extract over system_analytics_events) query paths. Both produce identical response shapes for collect, overview time-series, top-pages, referrer/UTM, and device breakdown.
Querying Analytics
Aggregated analytics are exposed through admin-gated read endpoints. Inspect raw events for debugging or custom dashboards.
| Endpoint | Returns |
|---|---|
POST /api/analytics/collect |
Public ingest endpoint hit by the tracking script. |
GET /api/analytics/overview |
Totals + bucketed daily time series. |
GET /api/analytics/pages |
Top pages by view count. |
GET /api/analytics/referrers |
Referrer and UTM-campaign breakdown. |
GET /api/analytics/devices |
Device-type and browser breakdown. |
GET /api/analytics/campaigns |
UTM campaign analysis. |
GET /api/analytics/events |
Admin-only raw event inspection (paginated, filterable). |
Inspecting raw events
GET /api/analytics/events provides row-level visibility into the unified stream — complementing the aggregated endpoints. It is admin-only and supports filtering by event_type, event_name, and date range.
GET /api/analytics/events?event_type=track&limit=50&offset=0&from=2026-01-01&to=2026-04-15
| Parameter | Description |
|---|---|
event_type |
Filter by type (page_view, track, …). |
event_name |
Exact-match event name. |
from / to |
ISO 8601 date range (inclusive). |
limit |
Max results (default 50, max 1000). |
offset |
Pagination offset (default 0). |
Privacy & Compliance
- No cookies, no fingerprinting. Visitors are identified by a server-computed SHA-256 hash; no client-side identifier is stored.
- DNT respected by default. Visitors sending
DNT:1are not tracked whenrespectDoNotTrackis on. - All data is local. No third-party services and no external calls — analytics never leaves your server.
- Bounded retention. Data older than
retentionDaysis automatically purged, aligning with the platform's data-minimisation posture (see Ecoconception).
Related Pages
- Admin Dashboard — operator read API across the platform.
- Activity Monitoring — audit trail of user and admin actions.
- Automations — emit custom
trackevents from automation actions. - Ecoconception — data retention and environmental footprint.
- Database Infrastructure — the SQLite/PostgreSQL parity behind the events table.