Documentation
Using Tastatur
One script tag installs it. Everything below is optional.
Install
Paste this into your <head>. That is the entire installation, and nothing below is required to get numbers.
<script defer data-site="YOUR_SITE_KEY" src="https://tastatur.dev/t.js"></script>
defer keeps it off the critical path. The script is about 3.2 KB over the
wire and makes one request per pageview. Visits from
localhost, private network ranges and file: are ignored, so
local development will not appear in your numbers.
Script options
Attributes on the script tag. All optional.
data-site
required
Your site key. Public by design; it appears in your page source.
data-api
optional
Override the ingest endpoint. Only needed if you proxy events through a different path than the script.
data-hash
optional
Count URL fragment changes as pageviews. For apps that route on #.
data-auto
default "true"
Set to "false" to stop automatic pageviews and send them yourself.
data-exclude
optional
Present means collect nothing. Useful on a staging build from the same codebase.
data-ignore-dnt
optional
Stop honouring Do Not Track and Global Privacy Control. Read the note under Opt-out before using this.
<script defer
data-site="YOUR_SITE_KEY"
data-hash
src="https://tastatur.dev/t.js"></script>
Custom events
Anything worth counting that is not a pageview: a signup, a download, a plan selected.
tastatur('event', 'Signup')
tastatur('event', 'Signup', { props: { plan: 'pro', source: 'pricing-page' } })
Event names are free text up to 120 characters. Keep them stable; renaming one starts a new series rather than renaming the old.
Properties are limited to 24 keys per event, 60 characters per key and 500 per value. Values may be strings, numbers or booleans.
Do not put personal data in properties. This is the
one field whose contents you control, and nothing in Tastatur can tell that
{ email: "…" } should not have been sent.
If you call tastatur() from code that may run before the deferred script has
loaded, add this stub first. It queues calls and replays them once the script is ready.
window.tastatur = window.tastatur || function () {
(window.tastatur.q = window.tastatur.q || []).push(arguments)
}
Revenue
Attach a monetary value to an event and it shows up against your goals.
tastatur('event', 'Purchase', {
revenue: { amount: 4900, currency: 'EUR' },
props: { plan: 'growth' }
})
amount is in minor units, so 4900 is €49.00. A currency is required
whenever an amount is sent: without one, a total would silently mix euros and yen.
Single-page apps
Handled automatically. There is nothing to configure for React, Vue, Svelte, Turbo, HTMX or hand-rolled routing.
The script patches history.pushState and replaceState, and
listens for popstate, so client-side navigation counts as a pageview.
Views are deduplicated against the last URL sent. Frameworks that call
replaceState during hydration would otherwise double-count every first
pageview.
Back/forward cache restores are counted via pageshow, because a restored
page does not re-run the script.
If you route on the URL fragment, add data-hash.
To take control yourself, set data-auto="false" and call it from your router:
router.afterEach(() => tastatur('pageview'))
Goals
A goal is a page or an event worth counting, with a conversion rate attached.
/pricing
Matches that page only.
/blog
Matches /blog and everything beneath it.
/blog/**
* matches inside one path segment, ** matches across segments.
Signup
Matches an event name you send.
Funnels
An ordered sequence of steps, with the drop-off between each.
Steps use the same matchers as goals. A visitor counts at step 3 only if they reached it after step 2, which means someone who backtracks and returns is still counted correctly.
Filtering and sharing
Click any row on the dashboard to filter everything by it.
Filter state lives in the URL, so a filtered view can be bookmarked, sent to a colleague, or kept open in a tab.
Filterable dimensions: page, entry page, source, referrer, country, browser,
operating system, device, screen class, every utm_* field, event name and
hostname.
Shared dashboards give a read-only link that needs no account, optionally password-protected and optionally expiring. They do not offer filters, because narrow filters on someone else's audience are a re-identification tool.
Content Security Policy
If your site sets a CSP, allow this origin in two directives.
script-src https://tastatur.dev;
connect-src https://tastatur.dev;
script-src lets the browser fetch t.js;
connect-src lets it send the beacon. The script needs no
unsafe-inline, no unsafe-eval, and sets no cookie, so it does
not interact with your cookie or frame policy at all.
Server-side events
The ingest endpoint is a plain HTTP API, so backend and mobile events work without the script.
curl -X POST https://tastatur.dev/api/event \
-H 'Content-Type: application/json' \
-H 'X-Forwarded-For: <the end user's IP>' \
-H 'User-Agent: <the end user's user-agent>' \
-d '{
"s": "YOUR_SITE_KEY",
"u": "https://example.com/checkout",
"n": "Purchase",
"v": 4900,
"c": "EUR"
}'
s
Site key
u
Absolute URL of the page or screen
n
Event name. Defaults to "pageview"
r
Referrer URL
w
Viewport width in pixels, bucketed server-side
p
Properties object
v
Revenue in minor units
c
Three-letter currency code
The endpoint answers 202 for everything, including a payload it rejected.
That is deliberate: a distinguishable response would let anyone test which site keys are
valid. Check your dashboard to confirm events are arriving.
Without JavaScript
A tracking pixel for visitors who run no scripts.
<noscript>
<img src="https://tastatur.dev/api/pixel?s=YOUR_SITE_KEY&u=https://example.com/"
alt="" width="1" height="1" referrerpolicy="no-referrer-when-downgrade">
</noscript>
The u parameter has to be written per page, so this is most practical in a
server-rendered layout where you can interpolate the current URL. Everything else
behaves the same, including bot filtering and the opt-out check.
Opt-out
Do Not Track and Global Privacy Control are honoured by default.
If a visitor's browser sends DNT: 1 or Sec-GPC: 1, no
identifier is computed and no event is stored. Only an anonymous per-hour counter is
incremented, so you can see that some requests opted out without any record of who.
This is checked in the script and again server-side, because the endpoint is a public API that an older cached script or the pixel might call without checking.
There is no per-person opt-out flag, because storing one would mean keeping a durable identifier for exactly the people who asked us not to.
data-ignore-dnt turns this off. A working opt-out is a condition of the
audience-measurement consent exemptions Tastatur is designed to qualify for, so
disabling it likely forfeits that argument. Take your own advice before using it.
Ad blockers
Some block Tastatur. You can proxy it through your own domain, and here is our position on doing so.
The script derives its endpoint from its own src, so proxying the script
through your domain automatically proxies the events with it. Point a path on your site
at this instance:
location /stats.js { proxy_pass https://tastatur.dev/t.js; }
location /stats/api { proxy_pass https://tastatur.dev/api/event; }
<script defer data-site="YOUR_SITE_KEY" src="/stats.js"></script>
Where we stand on this. Blockers exist because tracking earned them. Tastatur sets no cookie, stores nothing on the device and builds no cross-site profile, so a blocked Tastatur request protects nobody from anything.
That said, proxying is a way around a user's stated choice, and we will not pretend otherwise. It is documented because you will find it anyway and it is better done with the tradeoff in front of you. Note also that Do Not Track and Global Privacy Control are still honoured through a proxy, so a visitor who has explicitly objected remains uncounted.
What gets collected
The short version. The long version is a page of its own.
Page path
Query string stripped except utm_* tags. Personal data is removed from the path itself: emails, tokens, UUIDs and long ids are replaced with placeholders.
Referrer host
news.ycombinator.com, not the full referring URL.
Country
Two letters. No region, no city, no coordinates.
Browser and OS
Family plus major version only.
Device class
One of desktop, mobile, tablet.
Screen class
One of five buckets. The exact pixel width is never stored.
Visitor identifier
A truncated HMAC of a daily-rotating secret with the IP, user-agent and site. Unlinkable once the secret is destroyed.
Raw IP addresses and user-agent strings are never written to the database, a log, or disk. A returning visitor is counted as a new visitor tomorrow, so a 30-day figure is the sum of 30 daily figures rather than a count of distinct people.
Cookies, precisely
On your visitors' devices
Nothing
No cookie is set and none is read. Nothing is written to localStorage, sessionStorage or IndexedDB. No identifier is stored on the device in any form. This is the part that determines whether you need a consent banner for Tastatur, and it is the promise the product is built around.
On this dashboard, for you
Up to three
A first-party session cookie, set when you sign in, so you stay signed in. A second if you tick "stay signed in". A third if you use two-factor authentication and ask us not to send a code to this browser every time. All strictly necessary for a login and exempt from consent on that basis. They are set on our site, for account holders, and never touch the sites you measure or the people who visit them.
We say this explicitly because "no cookies" is used loosely in this category. A login needs a session cookie; pretending otherwise would be nonsense rather than privacy. What matters is that nothing of ours reaches your visitors.