Umami Self-Hosted Analytics and Tracking API | OpsBlu Docs

Umami Self-Hosted Analytics and Tracking API

Deploy Umami with Docker, configure umami.track() for custom events, query the REST API, and set up cookieless visitor tracking with PostgreSQL or MySQL.

How Umami Works

Umami is a self-hosted analytics platform built with Next.js. It collects data through a JavaScript tracker (script.js, roughly 2KB gzipped) that sends POST requests to a /api/send endpoint. Each request contains the page URL, referrer, screen dimensions, browser language, and a hashed session identifier.

Umami does not set cookies. Visitor uniqueness is calculated by hashing the IP address, User-Agent, hostname, and a rotating salt together. The hash changes daily, so Umami cannot recognize returning visitors across days. No personally identifiable information is stored in the database.

The data model consists of:

  • Sessions -- Identified by the daily hash. Contains browser, OS, device type, country, and language.
  • Pageviews -- URL path, referrer URL, and timestamp tied to a session.
  • Events -- Named custom events with optional JSON data properties, tied to a session.

All data is stored in PostgreSQL (recommended) or MySQL. Each tracked site is identified by a website ID (UUID), which is embedded in the tracking script.


Self-Hosted Deployment

Docker Compose

Umami provides an official Docker image. Create a docker-compose.yml:

services:
  umami:
    image: ghcr.io/umami-software/umami:postgresql-latest
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://umami:${DB_PASS}@db:5432/umami
      DATABASE_TYPE: postgresql
      APP_SECRET: ${APP_SECRET}
      # Optional: disable telemetry
      DISABLE_TELEMETRY: 1
    depends_on:
      db:
        condition: service_healthy
    restart: always

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

volumes:
  umami_db:

Start with:

export DB_PASS=$(openssl rand -hex 16)
export APP_SECRET=$(openssl rand -hex 32)
docker compose up -d

Default login: admin / umami. Change the password immediately after first login.

Environment Variables

Variable Required Description
DATABASE_URL Yes PostgreSQL or MySQL connection string
DATABASE_TYPE Yes postgresql or mysql
APP_SECRET Yes Secret for hashing, minimum 32 characters
DISABLE_TELEMETRY No Set to 1 to disable anonymous telemetry
TRACKER_SCRIPT_NAME No Custom filename for tracker (default: script.js)
COLLECT_API_ENDPOINT No Custom collection endpoint path (default: /api/send)
DISABLE_BOT_CHECK No Set to 1 to allow bot traffic
DISABLE_UPDATES No Set to 1 to skip update checks
CORS_MAX_AGE No CORS preflight cache duration in seconds

Reverse Proxy

Place behind Nginx or Caddy for TLS termination:

# Caddy
analytics.example.com {
    reverse_proxy localhost:3000
}
# Nginx
server {
    listen 443 ssl;
    server_name analytics.example.com;

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}

MySQL Backend

Replace the image tag and database URL:

umami:
  image: ghcr.io/umami-software/umami:mysql-latest
  environment:
    DATABASE_URL: mysql://umami:${DB_PASS}@db:3306/umami
    DATABASE_TYPE: mysql

Upgrading

docker compose pull
docker compose up -d

Umami applies database migrations automatically on startup.


Installing the Tracking Script

After creating a website in the Umami dashboard, you receive a website ID (UUID). Add the tracking script to your pages:

<script defer
  src="https://analytics.example.com/script.js"
  data-website-id="b59e9c65-ae32-4baa-87cf-1a6e508e3f45">
</script>

Script Attributes

Attribute Description
data-website-id Required. UUID of the website in Umami
data-auto-track Set to false to disable automatic pageview tracking
data-host-url Override the collection endpoint URL
data-domains Comma-separated list of domains to track (ignore others)
data-tag Tag for filtering in the dashboard

Excluding Your Own Visits

Add data-domains to restrict tracking to production domains:

<script defer
  src="https://analytics.example.com/script.js"
  data-website-id="YOUR_ID"
  data-domains="example.com,www.example.com">
</script>

Visits from localhost or staging domains are ignored.

Custom Script Filename

Rename the tracker to avoid ad-blocker detection. Set the environment variable:

TRACKER_SCRIPT_NAME=stats.js

Then reference the new filename:

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

Event Tracking

umami.track() API

Umami exposes a global umami.track() function for custom events:

// Track a named event
umami.track('signup-click');

// Track an event with properties
umami.track('purchase', {
  product: 'Pro Plan',
  price: 49.99,
  currency: 'USD'
});

// Track a custom pageview (virtual page)
umami.track({ url: '/virtual/thank-you', title: 'Thank You' });

// Override page data on an event
umami.track('form-submit', {
  url: '/contact',
  title: 'Contact Form'
});

Event names are case-sensitive. Properties are stored as JSON and can be queried via the API.

Data Attributes (No-Code Events)

Track clicks without writing JavaScript using HTML data attributes:

<button data-umami-event="signup-click">Sign Up</button>

<!-- With properties -->
<a href="/pricing"
   data-umami-event="pricing-click"
   data-umami-event-plan="enterprise">
  View Enterprise Plan
</a>

Any element with a data-umami-event attribute triggers an event on click. Additional data-umami-event-* attributes become event properties.

SPA Tracking

Umami automatically detects pushState and replaceState calls. For frameworks using the History API (React Router, Next.js, Vue Router), pageviews are tracked on route changes without configuration.

For hash-based routing, Umami tracks changes to location.hash by default.

Disabling Auto-Tracking

If you want full manual control:

<script defer
  src="https://analytics.example.com/script.js"
  data-website-id="YOUR_ID"
  data-auto-track="false">
</script>

<script>
  // Manually track pageviews and events
  umami.track();  // Track current page
  umami.track('custom-event');
</script>

REST API

Umami provides a REST API for reading analytics data and managing resources. Authentication uses a bearer token obtained from the login endpoint.

Authentication

# Get auth token
TOKEN=$(curl -s -X POST https://analytics.example.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"YOUR_PASSWORD"}' | jq -r '.token')

Website Stats

# Get aggregate stats for a website
curl "https://analytics.example.com/api/websites/WEBSITE_ID/stats?\
startAt=1704067200000&\
endAt=1706745600000" \
  -H "Authorization: Bearer $TOKEN"

Response:

{
  "pageviews": { "value": 45230, "prev": 38102 },
  "visitors": { "value": 12543, "prev": 10891 },
  "visits": { "value": 18320, "prev": 15400 },
  "bounces": { "value": 7800, "prev": 6900 },
  "totaltime": { "value": 2847000, "prev": 2340000 }
}

Timestamps are Unix milliseconds.

Pageview Data

# Pageviews over time
curl "https://analytics.example.com/api/websites/WEBSITE_ID/pageviews?\
startAt=1704067200000&\
endAt=1706745600000&\
unit=day" \
  -H "Authorization: Bearer $TOKEN"

Metrics Breakdown

# Top pages
curl "https://analytics.example.com/api/websites/WEBSITE_ID/metrics?\
startAt=1704067200000&\
endAt=1706745600000&\
type=url" \
  -H "Authorization: Bearer $TOKEN"

# Top referrers
curl "https://analytics.example.com/api/websites/WEBSITE_ID/metrics?\
startAt=1704067200000&\
endAt=1706745600000&\
type=referrer" \
  -H "Authorization: Bearer $TOKEN"

Valid type values: url, referrer, browser, os, device, country, event.

Event Data

# Get custom events
curl "https://analytics.example.com/api/websites/WEBSITE_ID/events?\
startAt=1704067200000&\
endAt=1706745600000&\
unit=day" \
  -H "Authorization: Bearer $TOKEN"

# Event name breakdown
curl "https://analytics.example.com/api/websites/WEBSITE_ID/metrics?\
startAt=1704067200000&\
endAt=1706745600000&\
type=event" \
  -H "Authorization: Bearer $TOKEN"

Managing Websites

# List all websites
curl "https://analytics.example.com/api/websites" \
  -H "Authorization: Bearer $TOKEN"

# Create a website
curl -X POST "https://analytics.example.com/api/websites" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"My Site","domain":"example.com"}'

# Get share URL token
curl "https://analytics.example.com/api/websites/WEBSITE_ID" \
  -H "Authorization: Bearer $TOKEN"

Share URLs

Umami generates public share URLs that display a read-only dashboard without authentication. Enable sharing in the website settings. The share URL format:

https://analytics.example.com/share/SHARE_TOKEN/Dashboard

Common Issues

Tracker script blocked by ad blockers: Rename the script file using TRACKER_SCRIPT_NAME=stats.js and the endpoint using COLLECT_API_ENDPOINT=/api/collect. Update the <script> tag accordingly.

Events not appearing in dashboard: Event names in umami.track('name') must match exactly what appears in the dashboard filter. Check for typos and case sensitivity. Events sent before the script loads are dropped.

Visitor counts seem low: Without cookies, Umami resets visitor identity daily. The same person visiting Monday and Tuesday counts as two visitors. This is by design. Compare unique visitors at weekly or monthly granularity for more stable numbers.

Database growing large: For high-traffic sites, Umami stores one row per pageview and event. On PostgreSQL, run periodic VACUUM ANALYZE and consider partitioning the website_event table by date. Set up automated cleanup with:

DELETE FROM website_event
WHERE created_at < NOW() - INTERVAL '365 days';

umami.track() not defined: The script loads with defer. If you call umami.track() in an inline script before the tracker loads, wrap it in a load handler:

window.addEventListener('load', () => {
  umami.track('page-loaded');
});

Docker container fails to start: Check that DATABASE_URL includes the correct credentials and that the database container is healthy. Umami runs Prisma migrations on startup -- if the database is unreachable, the container exits immediately.


Platform-Specific Considerations

PostgreSQL vs MySQL: PostgreSQL is recommended for better JSON query performance on event properties. MySQL support uses a separate Docker image (mysql-latest tag). Schema and migrations differ between the two -- you cannot switch databases after initial setup without a data migration.

Referrer Tracking Without Cookies: Umami captures the document.referrer value on each pageview. Since there are no cookies, referrer attribution is per-pageview rather than per-session. If a visitor arrives from Google and then navigates to three pages, only the first pageview has a referrer value.

Team and Role Management: Umami supports three roles: Admin (full access), User (view all sites), and View-Only (specific sites). Create teams in the dashboard and assign websites to team members. API tokens are scoped to the authenticated user's permissions.

Horizontal Scaling: Run multiple Umami containers behind a load balancer. All instances must share the same PostgreSQL database. There is no inter-process state -- the app is stateless. For high-write scenarios, ensure PostgreSQL has connection pooling (PgBouncer) configured.

Server-Side Event Collection: Send events without the JavaScript tracker by POSTing directly to the collection endpoint:

curl -X POST https://analytics.example.com/api/send \
  -H "Content-Type: application/json" \
  -H "User-Agent: Mozilla/5.0..." \
  -d '{
    "payload": {
      "hostname": "example.com",
      "language": "en-US",
      "referrer": "",
      "screen": "1920x1080",
      "title": "Order Complete",
      "url": "/order/complete",
      "website": "WEBSITE_ID",
      "name": "purchase"
    },
    "type": "event"
  }'

The User-Agent header is used for device detection. The hostname must match the website domain configured in Umami.