Insights.
Product analytics and crash tracking with consent built in.
A Haskell server over gRPC, PostgreSQL underneath, Grafana as the whole frontend. SDKs for Flutter, TypeScript and Haskell that persist nothing before consent and never break your app. Funnels, retention, cohorts, crash-free rates, a consent ledger and a generated privacy inventory, on your own machine.
MIT licensed. One NixOS module.
Why this exists
Hosted analytics asks you to ship your users' behaviour to someone else and to bolt consent on afterwards. Insights turns that around: consent is the first thing the SDK knows, the server refuses what was not granted, and the data stays in a database you own and can read with plain SQL.
What you get
The whole loop, from event to dashboard.
Everything a product team asks a tracker for, and the parts a privacy officer asks for, in one deployment.
01
Events
Dynamic events with typed properties, sessions and identity merge. Anonymous first, identified later, history intact.
02
Funnels, retention, cohorts
Declared next to the project in configuration, down to the property values an event must carry and the numbers to sum. Each declaration becomes a reporting view and a panel in the project's own dashboard; nothing is written in SQL.
03
Crash tracking
Fingerprinted issues with open, resolved and regressed states, crash-free users and sessions, breadcrumbs from the session's own events.
04
Consent ledger
Every grant and withdrawal recorded with its policy version. Two purposes: analytics and diagnostics.
05
Erasure
In-app erasure scoped to the device and its linked person, plus an operator function for requests that arrive by other means.
06
Inventory and manifest
PRIVACY.md and the Apple privacy manifest are generated from the contract, so a field cannot be collected undeclared.
07
Everything in Grafana
Eight shared dashboards, four alert rules, and a generated dashboard per environment in its own folder, on a vanilla Grafana with the built-in PostgreSQL datasource.
08
One NixOS module
The server, PostgreSQL with peer authentication, and Grafana provisioning. Projects are declared next to the products they measure, and ingest keys are generated by the service.
How it works
Four parts. One contract.
The proto under proto/peculiar/insights/v1 is the single source of truth for every language. Generated messages travel unchanged into the SDKs, and every collected field carries its data category and consent purpose.
01
The SDK batches.
Client-generated UUIDs, a sent-at timestamp for clock skew, and the consent snapshot on every batch. At least once, exactly once in effect.
02
The server decides.
Validation, consent enforcement, deduplication, identity merge and crash grouping, item by item, with a per-item outcome.
03
PostgreSQL keeps it.
Monthly partitions, per-project retention, a reporting schema of views and functions, and admin functions for triage and erasure.
04
Grafana shows it.
A read-only role over the reporting schema, provisioned dashboards and alerts, no community plugins.
SDKs
One vocabulary in four places.
Two objects everywhere: Insights is the process, Tracker records. A tracker is an immutable value; with (with_ in Dart) derives a child whose properties fold into everything it records.
Flutter
final insights = await FlutterInsights.start(
url: Uri.parse("https://insights.example.org"),
key: key,
consent: const ConsentPolicy.ask(),
);
final checkout = insights.tracker.with_({"screen": "checkout"});
await checkout.track("order_placed", {
"total": 42.5,
"items": 3,
});Haskell
withInsights config \insights -> do
let tracker = insights.subject subject consent
track tracker "order_placed"
["total" =: 42.5, "items" =: (3 :: Int)]
attempt tracker (placeOrder order)Flutter
iOS, Android, desktop and web. Error capture, lifecycle sessions, a durable queue, and an Apple privacy manifest shipped in the plugin.
TypeScript in the browser
gRPC-Web, IndexedDB queue, unhandled error capture, Global Privacy Control honoured as a denial.
TypeScript in Node
Native gRPC, file-backed queue, process error capture, per-request trackers for the users a backend serves.
Haskell
Records of functions for the application's Env, per-request trackers with the consent the backend holds, a stub server for tests.
How it is built
Principles, not promises.
P1
Consent before anything.
SDKs persist nothing and open no connection until a purpose is granted. A denial sends nothing.
P2
The server refuses unconsented data.
Every batch carries a consent snapshot. A defective client cannot store what was not granted.
P3
Configuration is declared.
Projects, retention, cohorts and analytics are configuration; keys are generated. Triage and erasure are Grafana actions only its Admins can take. There is no admin UI of its own to secure.
P4
Nothing hidden from the operator.
PostgreSQL holds the data, Grafana shows it, the migrations are readable SQL. No black box in the middle.
P5
Recording never breaks the app.
A recording call never throws in release. Problems flow into one diagnostics stream.
P6
MIT, and yours to run.
Self-hosted on your machine, your database, your dashboards. Easy to leave, nothing to export.
Start
Deploy it, then hand the guide to an agent.
Read the docs
Deploying with NixOS, the Grafana dashboards, operations, the consent model, and a document written for an agent to integrate the SDKs into your product.