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.

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

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

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

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

JavaScript
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:

JavaScript
router.afterEach(() => tastatur('pageview'))

Goals

A goal is a page or an event worth counting, with a conversion rate attached.

Exact path /pricing Matches that page only.
Starts with /blog Matches /blog and everything beneath it.
Wildcard /blog/** * matches inside one path segment, ** matches across segments.
Custom event Signup Matches an event name you send.
Goals are matched against history, so creating one immediately reports on data you already have. You do not need to set them up in advance.

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.

Pick a window of 24 hours or less. Visitor identifiers expire daily, so a funnel spanning more than a day cannot follow anyone across that boundary and will undercount. The funnel view says so when the window is longer, rather than quietly reporting a low number.

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.

CSP
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
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"
      }'
Pass the end user's address and user-agent, not your server's. Otherwise every server-side event collapses into one visitor located wherever your server is. This only works from a host your Tastatur instance trusts as a proxy.
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.

HTML
<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:

nginx
location /stats.js  { proxy_pass https://tastatur.dev/t.js; }
location /stats/api { proxy_pass https://tastatur.dev/api/event; }
HTML
<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.