Open-source, privacy-first web analytics platform — real-time dashboards, session replays, heatmaps, funnels, behavioral automations, and embeddable UI components.
Quick Start · Features · Components · Self-Hosting · Contributing · Deploy to Production
Seentics is a self-hosted web analytics platform that helps you understand your visitors and improve conversions. Track pageviews, custom events, and user behavior with heatmaps, session replays, and funnels — all without cookies, fingerprinting, or sending data to third parties.
You own your data. Deploy in minutes with Docker.
- Analytics — Dashboard, realtime, goals, funnels, events, comparisons, annotations
- Operations — Alerts, scheduled reports
- Behavior — Heatmaps, session replays, path analysis, on-site automations
- Developers — Tracking script, per-website API keys, webhooks,
@seentics/components, in-dashboard API docs
| Layer | Technology |
|---|---|
| Backend API | Bun, Hono, Drizzle ORM (TypeScript) |
| Frontend | Next.js 16, Tailwind CSS, shadcn/ui |
| Primary DB | PostgreSQL 15 (app data + analytics events in this OSS stack) |
| Object Storage | S3-compatible (MinIO in Docker Compose for local dev) |
Requirements: Docker and Docker Compose.
git clone https://github.com/Seentics/seentics.git
cd seentics
docker compose up -d --buildOpen http://localhost:3000. The API is served by core at http://localhost:8080/api/v1 (see docker-compose.yml).
After creating a site in the dashboard, add to your <head>:
<script
defer
src="http://localhost:3000/trackers/seentics.js"
data-website-id="YOUR_WEBSITE_ID"
></script>Use the same public URL your visitors load (dashboard on :3000 in Docker; in production, your web/edge URL). The ID is the website UUID from the Seentics dashboard.
// Basic
seentics.track('signup_click')
// With properties
seentics.track('purchase', { value: 49.99, plan: 'pro' })Create goals in Settings → Goals:
- Page visit — fires when a visitor hits a URL path (e.g.
/thank-you) - Custom event — fires when your code calls
seentics.track('event_name') - CSS selector — auto-fires when a visitor clicks a matching element (e.g.
#signup-btn)
Drop any chart or view into your own React app.
npm install @seentics/componentsimport {
SeenticsProvider,
AnalyticsSummary,
TrafficChart,
FunnelChart,
} from '@seentics/components'
<SeenticsProvider apiKey="YOUR_API_KEY" baseUrl="https://api.yourdomain.com">
{/* In a customer portal, admin panel, or your own product */}
<AnalyticsSummary siteId="site_abc" dateRange="last_7_days" />
<TrafficChart siteId="site_abc" metric="pageviews" groupBy="day" />
<FunnelChart siteId="site_abc" funnelId="funnel_xyz" />
</SeenticsProvider>Styled with CSS variables — integrates with any design system.
seentics/
├── core/ # Bun + Hono + Drizzle — single OSS API
├── web/ # Next.js app + dashboard UI
│ ├── trackers/ # `seentics.js` source (served under `/trackers/…`)
│ └── src/
│ ├── app/
│ │ └── websites/[websiteId]/
│ │ ├── page.tsx # Analytics overview
│ │ ├── realtime/ # Live dashboard
│ │ ├── goals/ # Goals page
│ │ ├── funnels/ # Funnels page
│ │ ├── events/ # Events page
│ │ ├── heatmaps/ # Heatmaps
│ │ ├── replays/ # Session replays
│ │ ├── paths/ # Path analysis
│ │ ├── automations/ # Behavioral automations
│ │ ├── api-keys/ # API key management
│ │ ├── ui-blocks/ # Embeddable components
│ │ ├── docs/ # API reference
│ │ └── settings/ # General, team, integrations, alerts
│ ├── components/ # UI components per section
│ ├── lib/ # API clients (one file per feature)
│ ├── hooks/ # Custom React hooks
│ └── stores/ # Zustand global state
│
├── packages/ # Embeddable UI packages (MIT licensed)
│ └── components/ # @seentics/components — React components
│
└── docker-compose.yml # Local dev stack
docker compose up -d --buildStarts PostgreSQL, MinIO, the Bun API (core), and the Next.js app (with dev hot reload on the pinned Compose file).
See DEPLOYMENT.md for hosting on a VPS or cloud instance. Configure secrets and DATABASE_URL for core, then run the stack with Docker (or your orchestrator) behind HTTPS.
Use .env.example where present and docker-compose.yml as the source of truth for ports and wired services.
| Variable | Role |
|---|---|
DATABASE_URL |
PostgreSQL |
JWT_SECRET |
Session / API auth |
GLOBAL_API_KEY |
Service-to-service / internal routes |
S3_* / S3_ENDPOINT |
Replay & heatmap object storage |
CORS_ALLOWED_ORIGINS |
Browser origins allowed to call the API |
TRUST_PROXY |
When true, trust X-Forwarded-For / cf-connecting-ip / x-real-ip for client IP (needed behind a reverse proxy for accurate GeoIP + rate limits) |
MAXMIND_DB_PATH |
Path inside the container to a GeoLite2-City or GeoIP2-City .mmdb file (local lookups only; no HTTP Geo API) |
MAXMIND_GEO_CACHE_MAX |
In-memory cap on cached IP → geo entries (default 50000) |
Analytics ingest resolves the visitor IP to country, region, and city via @maxmind/geoip2-node (GeoIP2-City / GeoLite2-City .mmdb). If the database is missing or has no match, country may still be set from edge headers (CF-IPCountry, X-Vercel-IP-Country, CloudFront-Viewer-Country). Values are stored on analytics_events (country, region, city).
- Download GeoLite2-City.mmdb from MaxMind and point
MAXMIND_DB_PATHat it (e.g../db/maxmind/GeoLite2-City.mmdbinside the container when./coreis bind-mounted at/app). - Schema: run
bun run db:pushfrom./coreso Postgres matches the Drizzle schema. If you previously addedgeo_latitude/geo_longitudemanually, applycore/db/sql/002_analytics_drop_geo_coordinates.sql(or letdb:pushdrop them). - Client IP: Bun sets
x-seentics-peer-ipfrom the TCP peer so GeoIP works withoutX-Forwarded-For. Behind nginx / a load balancer, setTRUST_PROXY=trueand forward the real client IP (X-Forwarded-For/X-Real-IP/CF-Connecting-IP); otherwise the peer may be the proxy, not the visitor. - Localhost / Docker: Private client IPs get
BDas the default country in non-production(ENVIRONMENT). Override withGEO_FALLBACK_COUNTRY(e.g.US). For real MaxMind geo, expose the app on a public URL soX-Forwarded-For/CF-Connecting-IPcarries the visitor’s public IP.
If the MMDB is missing, the API still runs; country / region / city on ingested events stay empty until MAXMIND_DB_PATH points at a valid City database.
Browser --> Next.js (:3000) -----> Bun API (:8080) -----> PostgreSQL
| |
+-----------------------------> MinIO
(replays / heatmaps; S3-compatible)
The tracker and dashboard call core (/api/v1/...). Analytics events are stored in PostgreSQL in this OSS stack; replays and heatmaps use S3-compatible storage (e.g. MinIO in Docker).
We welcome all contributions — bug reports, feature requests, docs, and code.
- Fork the repo
- Create a branch:
git checkout -b my-feature - Make your changes
- Submit a pull request
See CONTRIBUTING.md for dev setup.
Seentics is licensed under AGPL v3.0. You can self-host freely. Modifications must be open-sourced under the same license.
The @seentics/components package is MIT licensed.
Built by the Seentics team
