umami-software/umami

Umami is a modern, privacy-focused analytics platform. An open-source alternative to Google Analytics, Mixpanel and Amplitude.

36,211 stars TypeScript 8 components

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.

  1. 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
  2. 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]
  3. 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]
  4. 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]
  5. 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.

WebsiteEvent src/generated/prisma/client
Prisma 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
Session src/generated/prisma/client
Prisma 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
BoardComponentConfig src/lib/types
TypeScript 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
TeamMember src/generated/prisma/client
Prisma 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.

critical Contract weakly guarded

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
critical Shape unguarded

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
warning Ordering weakly guarded

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
critical Environment unguarded

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
warning Scale unguarded

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
info Contract unguarded

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
critical Domain weakly guarded

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
warning Temporal unguarded

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
warning Shape unguarded

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
info Resource unguarded

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

Events Database (database)
PostgreSQL/ClickHouse storing all website events, sessions, and user interactions — optimized for time-series analytics queries with indexes on websiteId and createdAt
React Query Cache (cache)
In-memory cache of API responses with automatic invalidation — reduces database load and provides instant dashboard updates when navigating between analytics views
Board Configuration Store (database)
Persisted JSON configurations defining dashboard layouts and component settings — enables custom analytics dashboards with drag-and-drop widget arrangements

Feedback Loops

Delays

Control Points

Technology Stack

Next.js (framework)
Full-stack React framework providing both dashboard UI and API routes for analytics collection and queries
Prisma (database)
Type-safe ORM handling all database operations with support for both PostgreSQL and ClickHouse analytics databases
React Query (library)
Server state management with caching and real-time updates for all dashboard analytics data
Chart.js (library)
Renders interactive time-series charts and visualizations for website analytics metrics
Rollup (build)
Bundles lightweight JavaScript tracker library for embedding in monitored websites
PostgreSQL/ClickHouse (database)
Primary database storing analytics events, sessions, and user data with optimized time-series queries

Key Components

Explore the interactive analysis

See the full architecture map, data flow, and code patterns visualization.

Analyze on CodeSea

Related 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 .