umami-software/umami
Umami is a modern, privacy-focused analytics platform. An open-source alternative to Google Analytics, Mixpanel and Amplitude.
Tracks website visitor behavior and displays privacy-focused analytics dashboards
Website visitors trigger JavaScript tracker events that POST to API routes, which validate and store events/sessions in database. Dashboard queries aggregate this data through Prisma ORM, returning metrics that React components transform into charts and tables. Board configurations control which analytics widgets display, with real-time updates via React Query invalidation.
Under the hood, the system uses 2 feedback loops, 3 data pools, 3 control points to manage its runtime behavior.
A 8-component fullstack. 834 files analyzed. Data flows through 5 distinct pipeline stages.
How Data Flows Through the System
Website visitors trigger JavaScript tracker events that POST to API routes, which validate and store events/sessions in database. Dashboard queries aggregate this data through Prisma ORM, returning metrics that React components transform into charts and tables. Board configurations control which analytics widgets display, with real-time updates via React Query invalidation.
- Track Website Events — JavaScript tracker on monitored websites captures pageviews, clicks, and custom events — serializes event data with session identifiers and POSTs to /api/collect endpoint
- Validate and Store Events — API routes authenticate tracking requests, validate event structure, extract user agent and IP location data, then persist events and sessions via Prisma to PostgreSQL/ClickHouse [WebsiteEvent → Session]
- Query Analytics Metrics — Dashboard components trigger useWebsiteMetricsQuery hooks that call /api/websites/{id}/metrics endpoints — Prisma aggregates events by time periods, pages, referrers, and geography [Session → Analytics Metrics]
- Render Dashboard Components — BoardComponentRegistry looks up component definitions from board configurations, instantiates appropriate React components (WebsiteChart, MetricsTable, WorldMap) with fetched metrics data [BoardComponentConfig → Chart Visualization]
- Update Real-time Data — React Query automatically refetches analytics data on configurable intervals — new events trigger cache invalidation causing dashboard components to re-render with latest metrics [Analytics Metrics → Table Display Data]
Data Models
The data structures that flow between stages — the contracts that hold the system together.
src/generated/prisma/clientPrisma model with id: string, websiteId: string, sessionId: string, createdAt: Date, url: string, eventName: string, eventData: Json
Created by tracker JavaScript when users interact with websites, stored in database, aggregated for dashboard metrics and real-time analytics
src/generated/prisma/clientPrisma model with id: string, websiteId: string, hostname: string, browser: string, os: string, device: string, screen: string, language: string, country: string, createdAt: Date
Generated when tracker detects new visitor session, enhanced with device/location data, used to group events and calculate user metrics
src/lib/typesTypeScript interface with type: string, props: Record<string, any> containing component type and configuration data for dashboard widgets
Created when users configure dashboard components, stored in board parameters, used to render appropriate analytics widgets with specified settings
src/generated/prisma/clientPrisma model with id: string, teamId: string, userId: string, role: TeamRole enum (owner, member, view-only), createdAt: Date
Created when users join teams, role determines website access permissions, validated for all analytics operations and admin functions
Hidden Assumptions
Things this code relies on but never validates. These are the things that cause silent failures when the system changes.
Assumes createMany operation succeeds silently even with invalid data - uses skipDuplicates: true but doesn't validate that inserted record count matches expected count
If this fails: If database constraints fail or data is malformed, some records silently fail to insert but the script continues, leading to incomplete seed data with no error indication
scripts/seed/index.ts:batchInsertSessions
Assumes all components in componentByEntityType map accept the same props interface as the default component, but provides no type enforcement between entity-specific and default components
If this fails: If a website component expects different props than a pixel component for the same board type, runtime errors occur when wrong component receives incompatible props
src/app/(main)/boards/boardComponentRegistry.tsx:ComponentDefinition
Assumes PIXEL_LINK_METRIC_TYPES filtering removes items that don't apply to pixels/links, but the filter logic only checks against a hardcoded subset - if METRIC_TYPES gains new items, they're included by default
If this fails: New metric types added to METRIC_TYPES automatically become available for pixel/link components even if those entity types don't support them, causing query failures or empty results
src/app/(main)/boards/boardComponentRegistry.tsx:METRIC_TYPES
Assumes DATABASE_URL environment variable points to a PostgreSQL database compatible with PrismaPg adapter, but doesn't validate database type or adapter compatibility
If this fails: If DATABASE_URL points to ClickHouse or another database type, PrismaPg adapter fails with cryptic connection errors instead of clear configuration guidance
scripts/seed/index.ts:PrismaPg
Assumes BATCH_SIZE of 1000 records fits within database connection limits and memory constraints for all deployment environments
If this fails: On resource-constrained servers or databases with low connection limits, batch inserts fail with out-of-memory errors or connection timeouts
scripts/seed/index.ts:BATCH_SIZE
Assumes all exported hook modules follow the same naming convention and export structure - exports everything with * but doesn't validate individual module exports
If this fails: If a query hook module exports non-hook functions or uses different naming patterns, they become part of the public API unintentionally, breaking consumer expectations
src/components/hooks/index.ts:export
Assumes components marked with requiresWebsite: true will only be used in website contexts, but provides no runtime validation that websiteId is available in component props
If this fails: Website-dependent components render in non-website boards (like pixel dashboards) and fail silently or crash when trying to access undefined website data
src/app/(main)/boards/boardComponentRegistry.tsx:requiresWebsite
Assumes generated seed data dates align with current system timezone and don't cross daylight saving time boundaries that could affect analytics aggregations
If this fails: Seed data spans DST transitions causing hour gaps/duplicates in time-series charts, making dashboard testing unrealistic compared to production data patterns
scripts/seed/index.ts:generateDatesBetween
Assumes optionsByEntityType keys match valid entity type strings ('website', 'pixel', 'link') but doesn't validate against an enumeration or type definition
If this fails: Typos in entity type keys cause config fields to disappear silently - users can't configure components properly and receive no error feedback
src/app/(main)/boards/boardComponentRegistry.tsx:ConfigField.optionsByEntityType
Assumes console output is available and supports progress bar formatting - doesn't handle environments where stdout is redirected or progress updates cause issues
If this fails: In Docker containers, CI environments, or log aggregation systems, progress bar output clutters logs with ANSI escape codes instead of clean progress indication
scripts/seed/index.ts:progressBar
System Behavior
How the system operates at runtime — where data accumulates, what loops, what waits, and what controls what.
Data Pools
PostgreSQL/ClickHouse storing all website events, sessions, and user interactions — optimized for time-series analytics queries with indexes on websiteId and createdAt
In-memory cache of API responses with automatic invalidation — reduces database load and provides instant dashboard updates when navigating between analytics views
Persisted JSON configurations defining dashboard layouts and component settings — enables custom analytics dashboards with drag-and-drop widget arrangements
Feedback Loops
- Real-time Analytics Updates (polling, reinforcing) — Trigger: React Query refetch intervals and user interactions. Action: Dashboard components query latest metrics from database and re-render visualizations. Exit: Component unmounts or user navigates away.
- Session Extension (recursive, reinforcing) — Trigger: New events from same visitor within session timeout. Action: Tracker updates existing session record instead of creating new one. Exit: 30-minute inactivity timeout expires.
Delays
- Analytics Query Processing (async-processing, ~100-500ms) — Dashboard shows loading states while Prisma aggregates events across time periods and dimensions
- Tracker Event Batching (batch-window, ~configurable) — Multiple rapid events may be batched before sending to reduce server load
- React Query Cache TTL (cache-ttl, ~5-30 seconds) — Analytics data may appear stale until cache invalidates and refetches from database
Control Points
- DATABASE_URL (env-var) — Controls: Database connection target (PostgreSQL vs ClickHouse) and performance characteristics. Default: postgresql://...
- Team Role Permissions (runtime-toggle) — Controls: Which analytics data and admin functions users can access based on team membership roles. Default: owner|member|view-only
- Board Component Types (architecture-switch) — Controls: Available dashboard widgets and their configuration options for custom analytics layouts
Technology Stack
Full-stack React framework providing both dashboard UI and API routes for analytics collection and queries
Type-safe ORM handling all database operations with support for both PostgreSQL and ClickHouse analytics databases
Server state management with caching and real-time updates for all dashboard analytics data
Renders interactive time-series charts and visualizations for website analytics metrics
Bundles lightweight JavaScript tracker library for embedding in monitored websites
Primary database storing analytics events, sessions, and user data with optimized time-series queries
Key Components
- BoardComponentRegistry (registry) — Maps component type strings to React components, icons, and configuration schemas — allows dynamic dashboard widget rendering based on stored board configurations
src/app/(main)/boards/boardComponentRegistry.tsx - WebsiteChart (processor) — Renders time-series analytics charts for pageviews, sessions, and events using Chart.js — processes raw metrics data into interactive visualizations
src/app/(main)/websites/[websiteId]/WebsiteChart.tsx - useWebsiteQuery (adapter) — React Query hook that fetches website configuration data from API, handles caching and real-time updates for website settings and metadata
src/components/hooks/queries/useWebsiteQuery.ts - PrismaClient (gateway) — Generated database client providing type-safe queries to PostgreSQL/ClickHouse — handles all persistence operations for analytics events, users, teams, and configurations
src/generated/prisma/client - MetricsTable (transformer) — Transforms raw analytics metrics into sortable, filterable table displays — supports pagination and real-time updates for various metric types (pages, referrers, countries)
src/components/metrics/MetricsTable.tsx - BoardProvider (orchestrator) — React context managing board state, layout operations, and component updates — coordinates between board configuration storage and dynamic component rendering
src/app/(main)/boards/BoardProvider.tsx - useApi (gateway) — Centralized API client providing authenticated HTTP operations with error handling — wraps fetch operations and manages JWT tokens for all server communication
src/components/hooks/useApi.ts - TeamProvider (orchestrator) — Manages team-scoped state and permissions validation — ensures users can only access websites and analytics data for teams they belong to
src/app/(main)/teams/TeamProvider.tsx
Explore the interactive analysis
See the full architecture map, data flow, and code patterns visualization.
Analyze on CodeSeaRelated Fullstack Repositories
Frequently Asked Questions
What is umami used for?
Tracks website visitor behavior and displays privacy-focused analytics dashboards umami-software/umami is a 8-component fullstack written in TypeScript. Data flows through 5 distinct pipeline stages. The codebase contains 834 files.
How is umami architected?
umami is organized into 4 architecture layers: Client Tracker, Web Application, API Layer, Data Layer. Data flows through 5 distinct pipeline stages. This layered structure keeps concerns separated and modules independent.
How does data flow through umami?
Data moves through 5 stages: Track Website Events → Validate and Store Events → Query Analytics Metrics → Render Dashboard Components → Update Real-time Data. Website visitors trigger JavaScript tracker events that POST to API routes, which validate and store events/sessions in database. Dashboard queries aggregate this data through Prisma ORM, returning metrics that React components transform into charts and tables. Board configurations control which analytics widgets display, with real-time updates via React Query invalidation. This pipeline design reflects a complex multi-stage processing system.
What technologies does umami use?
The core stack includes Next.js (Full-stack React framework providing both dashboard UI and API routes for analytics collection and queries), Prisma (Type-safe ORM handling all database operations with support for both PostgreSQL and ClickHouse analytics databases), React Query (Server state management with caching and real-time updates for all dashboard analytics data), Chart.js (Renders interactive time-series charts and visualizations for website analytics metrics), Rollup (Bundles lightweight JavaScript tracker library for embedding in monitored websites), PostgreSQL/ClickHouse (Primary database storing analytics events, sessions, and user data with optimized time-series queries). A focused set of dependencies that keeps the build manageable.
What system dynamics does umami have?
umami exhibits 3 data pools (Events Database, React Query Cache), 2 feedback loops, 3 control points, 3 delays. The feedback loops handle polling and recursive. These runtime behaviors shape how the system responds to load, failures, and configuration changes.
What design patterns does umami use?
4 design patterns detected: Component Registry Pattern, Query Hook Composition, Multi-tenant Scoping, Separate Build Artifacts.
Analyzed on April 20, 2026 by CodeSea. Written by Karolina Sarna.