data-* attributes on its <script> tag. It is Umami’s tracker with Lumenstat’s patches, so it reads Umami’s attribute names. This page lists every attribute tracker 1.0.0 reads, the umami object it exposes, and the request it sends. Only data-website-id is required.
src/tracker/index.ts and src/tracker/pow.ts in vendor/umami, which scripts/sync-umami.sh builds from Umami v3.5.0 and the patches in patches/umami. The proof of work is added by patch 0004.
How values are read
- Read once. The tracker reads its configuration when the script runs. Changing an attribute later has no effect.
- Strings only. An option that is on by default turns off only with the exact value
"false". An option that is off by default turns on only with the exact value"true".data-do-not-track="1"does nothing. - Global config. Each option can also come from a
window.umamiConfigobject defined before the tracker runs, under the camel-case name:websiteId,hostUrl,domains,autoTrackand so on. Adata-*attribute wins overumamiConfig. Arrays are joined with commas. Without a<script>element the tracker runs only ifumamiConfig.websiteIdis set.
Core
The website id (a UUID) from your project’s Setup page. Without it the tracker sends nothing. The proof of work is computed over this id.Example:
data-website-id="e676c9b4-11e4-4ef1-a4d7-87001773e9f2"Base URL of the collect endpoint. The tracker removes a trailing slash and posts to
<data-host-url>/e. The default is built into each release, so a copy of l.js served from your own domain still sends to https://t.lumenstat.com. Set it only when you run your own Lumenstat. The /e path is fixed in the release.Example: data-host-url="https://t.example.com"The hostnames the tracker may send from. When set, the tracker sends nothing unless
location.hostname equals one of the entries exactly. There is no subdomain or www. matching here, so list example.org and www.example.org separately.This is a filter in the browser. It is separate from the server’s origin check (layer 1), which accepts subdomains of the site’s registered domains. Use it to keep localhost and preview deployments from sending events at all.Example: data-domains="example.org,www.example.org"A label sent as
tag with every event from this script, for example to tell two variants of a page apart. A tag that starts with =, +, -, @, a tab or a carriage return is rejected by the collect endpoint with HTTP 400, a guard against spreadsheet formula injection.Example: data-tag="landing-b"Automatic tracking
"false" turns off everything automatic: the first page view, page views on client-side navigation, click events from data-umami-event, and performance metrics. What is still sent: your own umami.track and umami.identify calls, and the identify call for data-distinct-id.Example: data-auto-track="false""false" stops automatic page views, both the first one and those on client-side navigation. Click events and performance metrics keep working.Example: data-auto-pageview="false"Client-side navigation is detected by wrapping
history.pushState and history.replaceState. A page view is sent 300 ms after the URL changes, and only when the new URL differs from the previous one. The tracker does not listen to popstate or hashchange, so back and forward buttons and plain #anchor links do not send a page view."true" removes the query string from the page URL and the referrer before sending. This also removes utm_* parameters. Without it, the collect endpoint already drops every query parameter except utm_* (SCF_STRIP_QUERY).Example: data-exclude-search="true""true" removes the #fragment from the page URL and the referrer before sending. Without it, the fragment is stored as part of the page path.Example: data-exclude-hash="true""true" makes the tracker send nothing when the browser’s Do Not Track signal is on: navigator.doNotTrack, window.doNotTrack or navigator.msDoNotTrack equal to 1, "1" or "yes".Example: data-do-not-track="true"Sending
The name of a global function called before every send as
fn(type, payload). type is event, identify, performance or error. Return the payload, or a promise of it, to send it. Return a falsy value to drop it. The proof of work is computed after this callback, on the payload it returns, so you can rewrite url here. In umamiConfig, beforeSend can be the function itself.Example: data-before-send="scrubPayload"The
credentials mode of the collect request: omit, same-origin or include. The collect endpoint uses no cookies, so leave the default. Error reports always use omit.Example: data-fetch-credentials="omit"Identity and salts
Sets a distinct id for the visitor and sends an
identify call when the script loads. The id is mixed into the session id and stored, truncated to 50 characters.Example: data-distinct-id="cohort-2026-q4"A value the tracker sends as
salt with each event. It has no effect on Lumenstat. The server mixes a client salt into the session hash only when Umami’s ENABLE_CLIENT_SALT is set, and Lumenstat does not set it. Sessions use the random daily salt instead.Example: data-salt="2026-11-13"hourly, daily, weekly or monthly: builds salt from the current UTC time. Ignored when data-salt is set. No effect on Lumenstat, for the same reason as data-salt.Example: data-salt-rotation="daily"Performance and errors
The Lumenstat dashboard does not show performance or error data today."true" sends Web Vitals for each page view as a performance event: TTFB, FCP, LCP, CLS and INP. The event is sent once per page view, when the page is hidden or unloaded, on the next client-side navigation, or 10 seconds after load, whichever comes first. Performance events carry the proof-of-work header and go through the same checks as page views.Example: data-performance="true""true" reports uncaught errors and unhandled promise rejections, and turns on umami.captureException. A page sends at most 20 reports per minute and drops any report over 48,000 bytes. A report is sent up to three times when the request fails or gets a 5xx response. After a 429 the page stops sending for the Retry-After period, clamped to 1 to 300 seconds.Error reports are sent without x-lumenstat-pow, and the collect endpoint handles them before the four quality checks, so none of those checks run on them. Umami’s user-agent bot check does.Example: data-errors="true"A release name attached to error reports, up to 100 characters.Example:
data-release="web@2.4.1"An environment name attached to error reports, up to 50 characters.Example:
data-environment="staging"Click events
Adddata-umami-event to any element to send a custom event when it is clicked. Each data-umami-event-<key> attribute on the same element adds a property, always as a string.
data-umami-event, so clicks on child elements count. For a link that opens in the same tab, the tracker holds the navigation, sends the event, then follows the link (in the top window for target="_top"). Links with target="_blank", and Ctrl, Shift, Cmd or middle clicks, navigate normally while the event is sent.
Opting out a browser
Run this once in a browser’s console to stop that browser from sending events, for example on your own machines:umami.disabled is also skipped. The tracker reads this one local storage key and never writes to local storage.
JavaScript API
The first tracker that runs on a page createswindow.umami. A second tracker on the same page does not replace it. The object exists only after the script has run, and calls made earlier are not queued, so check for it when you call it from code that may run first.
The default properties are
website, hostname, url, referrer, title, screen, language, tag, id (the distinct id) and salt. url is the full page URL after data-exclude-search and data-exclude-hash are applied. A same-origin referrer is sent as a path only.
The promises resolve when the request completes and never reject. They resolve without sending when tracking is off: no website id, the browser opted out, the hostname is not in data-domains, or Do Not Track is on with data-do-not-track="true".
Limits on names and data
- Event names longer than 50 characters are truncated when stored.
- An event name that starts with
=,+,-,@, a tab or a carriage return is rejected with HTTP 400. - Event data is any JSON object. A number whose absolute value is
1e15or more (outside an array), or nesting deeper than 32 levels, is rejected with HTTP 400. - Umami’s tracker types document further limits: strings up to 500 characters, arrays stored as strings, at most 50 properties per object.
The collect request
Every event is aPOST to <data-host-url>/e with keepalive: true and this JSON body:
type is event for page views and custom events, identify or performance. Custom events add name and data.
The proof of JavaScript, as
<slot>.<nonce>.slotis the 10-minute window the event was sent in:floor(unix time in ms / 600000).nonceis the first non-negative integer for whichsha256("<website>:<slot>:<url>:<nonce>")starts with at least 10 zero bits.websiteandurlare the payload’s values exactly as sent, and the string is UTF-8.
SCF_POW_BITS zero bits (10 on the hosted service). A missing header flags the event as no_proof. A malformed, stale or insufficient one flags it as bad_proof. See Flag reasons.Example, for the payload above at 12:00 UTC on 13 November 2026: x-lumenstat-pow: 2990952.213. The digest starts 00156481…, which is 11 zero bits.The cache token from the previous response on this page, once there is one. The server uses it to keep later events on the page in the same session.
Origin and the Fetch Metadata headers (Sec-Fetch-Mode, Sec-Fetch-Site) itself. Layer 1 of the bot and inflation defense depends on them.
A counted event gets {"cache": "…", "sessionId": "…", "visitId": "…"}. An event rejected as a bot, or flagged by a quality check, gets {"beep": "boop"}. The reply does not say which check failed.