Analytics beginner ⏱ 20-30 minutes

Umami Analytics: Simple, Fast, Privacy-Focused Website Analytics

Deploy Umami Analytics for lightweight, self-hosted web analytics. Fast setup, real-time data, and GDPR compliant tracking.

By Homelab Team • 2025-01-15
#analytics#privacy#docker#self-hosted#real-time

Introduction

Umami (Japanese for “pleasant taste”) delivers exactly what its name suggests: analytics that are simple, clean, and satisfying to use. Born from frustration with bloated analytics platforms, Umami offers a refreshingly minimal approach without sacrificing essential features.

With over 20,000 GitHub stars and powering analytics for thousands of websites, Umami has become the go-to choice for developers who want straightforward analytics without complexity or privacy concerns.

What You’ll Learn:

  • Deploy Umami with Docker in under 20 minutes
  • Install tracking on any website or app
  • Create real-time dashboards with custom metrics
  • Set up event tracking for user interactions
  • Share analytics with team members or clients
  • Configure multi-website tracking from one installation

Prerequisites:

  • Docker and Docker Compose installed (v20.10+ recommended)
  • Basic command-line knowledge
  • 500MB+ free disk space (1-2GB for multiple sites)
  • (Optional) Domain name for SSL access

Why Umami Analytics?

The Umami Philosophy

Umami was built on three core principles:

  1. Simplicity: No clutter, just the metrics that matter
  2. Speed: Blazing fast interface and sub-1KB tracking script
  3. Privacy: No cookies, no personal data, fully compliant

Key Advantages

Developer-Friendly:

  • Clean, modern interface built with React and Next.js
  • Simple API for custom integrations
  • One-line installation for most frameworks
  • Real-time updates without page refresh

Lightweight Performance:

  • Tracking script under 2KB (compressed)
  • PostgreSQL or MySQL backend (your choice)
  • Minimal server requirements (runs on Raspberry Pi)
  • Fast dashboard loads even with millions of events

Privacy-First:

  • Cookie-free tracking (no consent banners needed)
  • GDPR, CCPA, and PECR compliant
  • IP addresses hashed and anonymized
  • No cross-site tracking or fingerprinting

Multi-Tenant Ready:

  • Track unlimited websites from one installation
  • Team collaboration with role-based access
  • Share individual dashboards publicly or privately
  • Client reporting with white-label options

Feature Comparison

FeatureUmamiPlausibleMatomoGoogle Analytics
Script Size2KB1KB22KB45KB+
Setup Time15 min30 min45 min10 min
Real-timeYesYesYesDelayed
Multi-siteUnlimitedUnlimitedUnlimitedLimited
Team FeaturesYesLimitedYesYes
Event TrackingBuilt-inBuilt-inAdvancedComplex
API AccessFull RESTFull RESTPartialLimited
Self-hostedYesYesYesNo

Understanding Umami Architecture

System Components

Website → Tracking Script (2KB) → Umami API → Database (PostgreSQL/MySQL)
                                       ↓
                                  Dashboard (Next.js)

Stack:

  • Frontend: Next.js (React) with real-time updates
  • Backend: Node.js API with efficient data processing
  • Database: PostgreSQL (recommended) or MySQL
  • Tracker: Vanilla JavaScript, framework-agnostic

What Gets Tracked:

  • Page URLs and referrers
  • Browser, OS, and device type
  • Country from IP (IP not stored)
  • Screen resolution categories
  • Custom events you define

Privacy Features:

  • IP addresses salted, hashed, and never stored
  • Daily salts ensure no long-term visitor identification
  • No cookies or persistent storage
  • No third-party requests

Step 1: Create Project Directory

mkdir -p ~/umami
cd ~/umami

Step 2: Create docker-compose.yml

Production-ready configuration:

version: '3'

services:
  umami:
    image: ghcr.io/umami-software/umami:postgresql-latest
    container_name: umami
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://umami:umami@db:5432/umami
      DATABASE_TYPE: postgresql
      APP_SECRET: replace-me-with-random-string
      # Optional: Enable cloud storage for backups
      # CLOUD_MODE: 1
      # CLOUD_URL: https://cloud.umami.is
    depends_on:
      db:
        condition: service_healthy
    restart: always
    networks:
      - umami-network

  db:
    image: postgres:15-alpine
    container_name: umami_db
    environment:
      POSTGRES_DB: umami
      POSTGRES_USER: umami
      POSTGRES_PASSWORD: umami
    volumes:
      - umami-db-data:/var/lib/postgresql/data
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - umami-network

volumes:
  umami-db-data:

networks:
  umami-network:
    driver: bridge

For MySQL instead:

services:
  umami:
    image: ghcr.io/umami-software/umami:mysql-latest
    environment:
      DATABASE_URL: mysql://umami:umami@db:3306/umami
      DATABASE_TYPE: mysql
      APP_SECRET: replace-me-with-random-string

  db:
    image: mysql:8
    environment:
      MYSQL_DATABASE: umami
      MYSQL_USER: umami
      MYSQL_PASSWORD: umami
      MYSQL_ROOT_PASSWORD: root-password

Step 3: Generate APP_SECRET

# Generate secure random string
openssl rand -hex 32

Copy the output and replace replace-me-with-random-string in docker-compose.yml

Step 4: Start Umami

# Start all services
docker compose up -d

# Check status
docker compose ps

# Watch initialization logs
docker compose logs -f umami

Expected output:

umami  | Loaded env from .env
umami  | ready - started server on 0.0.0.0:3000, url: http://localhost:3000
umami  | Database connection established.

Step 5: Access Dashboard

Navigate to: http://YOUR_SERVER_IP:3000

Default credentials:

  • Username: admin
  • Password: umami

IMPORTANT: Change the default password immediately!

  1. Log in with default credentials
  2. Click profile icon (top right)
  3. Select “Profile”
  4. Click “Change Password”
  5. Enter new secure password
  6. Save changes

Alternative Installation: MySQL Backend

If you prefer MySQL over PostgreSQL:

version: '3'

services:
  umami:
    image: ghcr.io/umami-software/umami:mysql-latest
    container_name: umami
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: mysql://umami:umami@db:3306/umami
      DATABASE_TYPE: mysql
      APP_SECRET: your-generated-secret-here
    depends_on:
      - db
    restart: always

  db:
    image: mysql:8
    container_name: umami_mysql
    environment:
      MYSQL_ROOT_PASSWORD: your-root-password
      MYSQL_DATABASE: umami
      MYSQL_USER: umami
      MYSQL_PASSWORD: umami
    volumes:
      - umami-mysql-data:/var/lib/mysql
    restart: always

volumes:
  umami-mysql-data:

Adding Your First Website

Step 1: Create Website in Umami

  1. Log into Umami dashboard
  2. Click “Settings” in sidebar
  3. Select “Websites” tab
  4. Click ”+ Add website”
  5. Fill in details:
    • Name: My Website
    • Domain: example.com (without https://)
    • Enable Share URL: Toggle if you want public sharing
  6. Click “Save”

Step 2: Get Tracking Code

  1. Click the website you just created
  2. Click “Edit” button
  3. Select “Tracking code” tab
  4. Copy the code snippet

Example tracking code:

<script defer src="http://YOUR_SERVER:3000/script.js" data-website-id="abc123def-4567-890-abc-def123456789"></script>

Step 3: Install Tracking Code

HTML websites: Add before closing </head> tag:

<!DOCTYPE html>
<html>
<head>
    <title>My Website</title>
    <script defer src="https://analytics.yourdomain.com/script.js" data-website-id="abc123def-4567-890-abc-def123456789"></script>
</head>
<body>
    <!-- Your content -->
</body>
</html>

React/Next.js: Add to pages/_app.js or app/layout.js:

import Script from 'next/script'

export default function App({ Component, pageProps }) {
  return (
    <>
      <Script
        defer
        src="https://analytics.yourdomain.com/script.js"
        data-website-id="abc123def-4567-890-abc-def123456789"
      />
      <Component {...pageProps} />
    </>
  )
}

Vue.js: Add to public/index.html or use vue-meta:

// In main.js or App.vue
export default {
  mounted() {
    const script = document.createElement('script');
    script.defer = true;
    script.src = 'https://analytics.yourdomain.com/script.js';
    script.setAttribute('data-website-id', 'abc123def-4567-890-abc-def123456789');
    document.head.appendChild(script);
  }
}

WordPress: Use “Insert Headers and Footers” plugin or add to theme’s header.php:

<script defer src="https://analytics.yourdomain.com/script.js" data-website-id="abc123def-4567-890-abc-def123456789"></script>

Ghost: Settings → Code Injection → Site Header

Gatsby: Install gatsby-plugin-umami:

npm install gatsby-plugin-umami
// gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: 'gatsby-plugin-umami',
      options: {
        websiteId: 'abc123def-4567-890-abc-def123456789',
        srcUrl: 'https://analytics.yourdomain.com/script.js',
      },
    },
  ],
}

Step 4: Verify Installation

Method 1: Real-time Dashboard

  1. Visit your website
  2. Open Umami dashboard
  3. Click “Realtime” in sidebar
  4. Should see your visit immediately

Method 2: Browser DevTools

  1. Visit your website
  2. Open DevTools (F12) → Network tab
  3. Search for “script.js”
  4. Should see 200 OK response
  5. Look for /api/send POST request (the tracking beacon)

Method 3: Console Test In browser console on your website:

// Check if umami object exists
console.log(window.umami);

// Manually trigger test event
umami.track('test-event');

Custom Event Tracking

Track specific user actions beyond pageviews.

Basic Event Tracking

Button clicks:

<button onclick="umami.track('signup-button')">Sign Up</button>

Form submissions:

<form onsubmit="umami.track('newsletter-signup'); return true;">
    <input type="email" name="email" required>
    <button type="submit">Subscribe</button>
</form>

Link clicks:

<a href="/download.pdf" onclick="umami.track('download', { file: 'guide.pdf' })">
    Download Guide
</a>

Event Tracking with Properties

Track additional context with events:

// E-commerce purchase
umami.track('purchase', {
    product: 'Premium Plan',
    price: 49.99,
    currency: 'USD'
});

// Video playback
umami.track('video-play', {
    title: 'Product Demo',
    duration: 180,
    quality: '1080p'
});

// Feature usage
umami.track('feature-used', {
    feature: 'export',
    format: 'csv'
});

Tracking with React

import { useEffect } from 'react';

function PricingPage() {
    // Track page view with custom data
    useEffect(() => {
        umami.track('pricing-page-view', {
            source: 'nav-menu'
        });
    }, []);

    const handlePurchase = (plan) => {
        umami.track('purchase-started', {
            plan: plan,
            price: getPlanPrice(plan)
        });
        // Continue with purchase flow
    };

    return (
        <button onClick={() => handlePurchase('pro')}>
            Buy Pro Plan
        </button>
    );
}

Tracking with Vue

export default {
  methods: {
    trackDownload(filename) {
      window.umami.track('file-download', {
        filename: filename,
        timestamp: new Date().toISOString()
      });
    }
  }
}

Viewing Custom Events

  1. Go to your website’s dashboard
  2. Click “Events” tab
  3. See all tracked events with counts
  4. Click on event name to see event properties
  5. Filter by date range or event type

Team Collaboration

User Roles

Umami supports three user roles:

RolePermissions
AdminFull system access, manage all users and websites
UserAccess assigned websites, cannot create new sites
View OnlyRead-only access to assigned websites

Adding Team Members

  1. Log in as admin
  2. Go to “Settings” → “Users”
  3. Click ”+ Add user”
  4. Fill in details:
    • Username: team-member
    • Password: Generate secure password
    • Role: Select appropriate role
  5. Click “Save”
  6. Assign websites to user:
    • Go to “Websites” tab
    • Click website → “Edit”
    • Select “Team” tab
    • Add user to team
    • Choose access level (View/Edit)

Sharing Dashboards

Option 1: Share URL (Public)

  1. Go to website settings
  2. Enable “Enable Share URL”
  3. Copy the generated share URL
  4. Share with anyone (no login required)

Option 2: Team Access (Private)

  1. Add team members as users
  2. Assign them to specific websites
  3. They log in with their credentials
  4. See only their assigned websites

Share URL example:

https://analytics.yourdomain.com/share/abc123def/my-website

Anyone with this URL can view analytics (read-only).


Reverse Proxy Configuration

Nginx Configuration

# /etc/nginx/sites-available/umami
server {
    listen 443 ssl http2;
    server_name analytics.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/analytics.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/analytics.yourdomain.com/privkey.pem;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

# HTTP redirect
server {
    listen 80;
    server_name analytics.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

Enable and reload:

sudo ln -s /etc/nginx/sites-available/umami /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Caddy Configuration

# /etc/caddy/Caddyfile
analytics.yourdomain.com {
    reverse_proxy localhost:3000
}

Reload:

sudo systemctl reload caddy

Traefik Configuration

Add labels to docker-compose.yml:

services:
  umami:
    image: ghcr.io/umami-software/umami:postgresql-latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.umami.rule=Host(`analytics.yourdomain.com`)"
      - "traefik.http.routers.umami.entrypoints=websecure"
      - "traefik.http.routers.umami.tls.certresolver=letsencrypt"
      - "traefik.http.services.umami.loadbalancer.server.port=3000"
    networks:
      - traefik-proxy
      - umami-network

networks:
  traefik-proxy:
    external: true
  umami-network:
    driver: bridge

Advanced Features

Environment Variables

Complete list of configuration options for docker-compose.yml:

environment:
  # Required
  DATABASE_URL: postgresql://umami:umami@db:5432/umami
  DATABASE_TYPE: postgresql
  APP_SECRET: your-secret-key

  # Optional
  PORT: 3000                           # Change default port
  HOSTNAME: 0.0.0.0                    # Bind address
  DISABLE_LOGIN: 0                     # Set to 1 to disable login (public mode)
  DISABLE_UPDATES: 0                   # Disable update checks
  FORCE_SSL: 0                         # Force HTTPS (set to 1 behind proxy)
  LOG_QUERY: 0                         # Log database queries (debug)
  TRACKER_SCRIPT_NAME: script.js       # Rename tracker script
  COLLECT_API_ENDPOINT: /api/send      # Rename collection endpoint
  REMOVE_TRAILING_SLASH: 0             # Remove trailing slashes from URLs
  CLIENT_IP_HEADER: X-Forwarded-For    # Header for client IP (behind proxy)

Custom Tracker Script Name

To avoid ad blockers, rename the tracking script:

environment:
  TRACKER_SCRIPT_NAME: analytics.js
  COLLECT_API_ENDPOINT: /api/collect

Update tracking code:

<script defer src="https://analytics.yourdomain.com/analytics.js" data-website-id="..."></script>

Proxying Through Your Domain

Serve Umami tracker from your main website domain:

Nginx configuration:

# On your main website server
location /stats/ {
    proxy_pass https://analytics.yourdomain.com/;
    proxy_set_header Host $host;
}

Update tracking code:

<script defer src="/stats/script.js" data-website-id="..."></script>

API Access

Umami provides a full REST API for automation.

Get API token:

  1. Settings → Profile → Generate API Key
  2. Copy the token

Example API calls:

# Get website stats
curl -X GET "https://analytics.yourdomain.com/api/websites/YOUR_WEBSITE_ID/stats"   -H "Authorization: Bearer YOUR_API_TOKEN"   -H "Content-Type: application/json"

# Get realtime data
curl -X GET "https://analytics.yourdomain.com/api/websites/YOUR_WEBSITE_ID/realtime"   -H "Authorization: Bearer YOUR_API_TOKEN"

# Get events
curl -X GET "https://analytics.yourdomain.com/api/websites/YOUR_WEBSITE_ID/events?startAt=1640000000000&endAt=1650000000000"   -H "Authorization: Bearer YOUR_API_TOKEN"

Python example:

import requests

API_URL = "https://analytics.yourdomain.com/api"
API_TOKEN = "your-api-token"
WEBSITE_ID = "your-website-id"

headers = {
    "Authorization": f"Bearer {API_TOKEN}",
    "Content-Type": "application/json"
}

# Get website statistics
response = requests.get(
    f"{API_URL}/websites/{WEBSITE_ID}/stats",
    headers=headers,
    params={
        "startAt": 1640000000000,
        "endAt": 1650000000000
    }
)

stats = response.json()
print(f"Pageviews: {stats['pageviews']['value']}")
print(f"Visitors: {stats['visitors']['value']}")

GDPR Compliance

Why Umami Is GDPR Compliant

No Personal Data:

  • IP addresses hashed with daily-rotated salt
  • No cookies or persistent identifiers
  • No cross-site tracking
  • No user profiles or behavioral data

Data Processing:

  • All data aggregate and statistical
  • Cannot identify individuals
  • Salt rotation prevents long-term tracking
  • Complies with “Privacy by Design”

Consent Not Required: Umami doesn’t require cookie consent because:

  1. No cookies used
  2. No personal data collected
  3. Processing based on legitimate interest (Article 6(1)(f) GDPR)

Privacy Policy Language

Add to your privacy policy:

Website Analytics

We use Umami Analytics, a privacy-friendly analytics tool, to understand website
usage patterns. Umami does not use cookies and does not collect personal data.

Information collected (all anonymized):
- Page URLs visited
- Referring website
- Browser and device type
- Country (derived from IP, which is immediately hashed and discarded)

All data is aggregated and cannot be used to identify individual visitors.
Analytics data is hosted on our own servers and never shared with third parties.

For more information: https://umami.is/docs/privacy

Data Deletion

Delete analytics for a specific website:

  1. Settings → Websites
  2. Click website → Edit
  3. Click “Delete” button
  4. Confirm deletion

Note: This deletes all analytics data permanently.


Troubleshooting Common Issues

Issue 1: Tracking Not Working

Diagnosis:

# Check Umami logs
docker compose logs umami

# Test tracking endpoint
curl -X POST https://analytics.yourdomain.com/api/send   -H "Content-Type: application/json"   -d '{"type":"pageview","payload":{"website":"YOUR_WEBSITE_ID","url":"/test"}}'

Solutions:

  1. Verify website ID matches in tracking code
  2. Check CORS settings if tracking from different domain
  3. Ensure script loads (check Network tab in DevTools)
  4. Disable browser ad blockers for testing
  5. Verify CLIENT_IP_HEADER if behind proxy

Issue 2: Database Connection Failed

Symptoms: Umami won’t start, logs show database errors

Solutions:

# Check database container
docker compose logs db

# Verify database is healthy
docker exec umami_db pg_isready -U umami

# Restart everything
docker compose down
docker compose up -d

# Check DATABASE_URL format
# Correct: postgresql://user:pass@host:5432/dbname

Issue 3: High Memory Usage

Symptoms: Container using excessive RAM

Solution: Limit container resources in docker-compose.yml:

services:
  umami:
    deploy:
      resources:
        limits:
          memory: 512M

  db:
    deploy:
      resources:
        limits:
          memory: 1G

Issue 4: Slow Dashboard Loading

For high-traffic sites:

  1. Optimize PostgreSQL:
db:
  command: postgres -c shared_buffers=256MB -c max_connections=200
  1. Add database indexes:
docker exec -it umami_db psql -U umami -d umami

# Add custom indexes for better performance
CREATE INDEX IF NOT EXISTS idx_event_created_at ON event(created_at);
CREATE INDEX IF NOT EXISTS idx_event_website_id ON event(website_id);
  1. Consider database on separate server for production

Issue 5: Cannot Login After Password Change

Reset admin password:

# Access database
docker exec -it umami_db psql -U umami -d umami

# Reset password to 'umami'
UPDATE account SET password = '$2b$10$BUli0c.muyCW1ErNJc3jL.vFRFtFJWrT8/GcR4A.sUdCznaXiqFXa' WHERE username = 'admin';

Then log in with username admin and password umami, then change it again.


Backup and Restore

Automated Backup Script

#!/bin/bash
# backup-umami.sh

BACKUP_DIR="/backups/umami"
DATE=$(date +%Y%m%d-%H%M%S)

mkdir -p "$BACKUP_DIR"

# Backup PostgreSQL database
docker exec umami_db pg_dump -U umami umami | gzip > "$BACKUP_DIR/umami-$DATE.sql.gz"

# Backup docker-compose config
cp ~/umami/docker-compose.yml "$BACKUP_DIR/docker-compose-$DATE.yml"

# Remove backups older than 30 days
find "$BACKUP_DIR" -type f -mtime +30 -delete

echo "Backup completed: $BACKUP_DIR/umami-$DATE.sql.gz"

Automate with cron:

chmod +x backup-umami.sh

# Run daily at 2 AM
crontab -e
# Add: 0 2 * * * /root/backup-umami.sh >> /var/log/umami-backup.log 2>&1

Restore from Backup

# Stop Umami
docker compose stop umami

# Restore database
gunzip -c /backups/umami/umami-YYYYMMDD-HHMMSS.sql.gz |   docker exec -i umami_db psql -U umami -d umami

# Restart Umami
docker compose start umami

Migration from Google Analytics

Export GA Data

  1. In Google Analytics, go to Admin
  2. Select Property → Property Settings
  3. Export your historical data (if needed)

Import Historical Data

Umami doesn’t support direct GA import, but you can:

  1. Continue running GA alongside Umami for comparison period
  2. Use GA API to fetch historical data and import via Umami API
  3. Start fresh with Umami (recommended for most cases)

Update Tracking Codes

Replace GA tracking with Umami:

Remove:

<!-- Remove Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
</script>

Add:

<!-- Add Umami -->
<script defer src="https://analytics.yourdomain.com/script.js" data-website-id="YOUR_WEBSITE_ID"></script>

Performance Optimization

For High-Traffic Sites

1. Database optimization:

db:
  environment:
    POSTGRES_SHARED_BUFFERS: 512MB
    POSTGRES_EFFECTIVE_CACHE_SIZE: 2GB
    POSTGRES_MAX_CONNECTIONS: 200

2. Enable connection pooling:

Use PgBouncer for connection pooling:

services:
  pgbouncer:
    image: pgbouncer/pgbouncer
    environment:
      DATABASES_HOST: db
      DATABASES_PORT: 5432
      DATABASES_USER: umami
      DATABASES_PASSWORD: umami
      DATABASES_DBNAME: umami
      PGBOUNCER_POOL_MODE: transaction
      PGBOUNCER_MAX_CLIENT_CONN: 1000
      PGBOUNCER_DEFAULT_POOL_SIZE: 25
    depends_on:
      - db

  umami:
    environment:
      DATABASE_URL: postgresql://umami:umami@pgbouncer:6432/umami

3. Monitor resource usage:

docker stats umami umami_db

Conclusion

You now have a production-ready Umami analytics system that provides:

  • Fast, real-time analytics without compromising privacy
  • Simple, intuitive dashboards anyone can understand
  • Multi-website tracking from a single installation
  • GDPR compliance without cookie consent complexity
  • Full API access for custom integrations

Next Steps:

  1. Add tracking to all your websites
  2. Set up custom event tracking for key actions
  3. Create team accounts for collaborators
  4. Configure automated backups
  5. Explore the API for custom dashboards

Remember: Umami’s strength is its simplicity. Start with basic page tracking, add custom events as needed, and enjoy analytics that just work.


Additional Resources

Official:

Community:

Integrations:


Last Updated: January 2025 Version Covered: Umami v2.10+