takt

Privacy

Privacy protection is built into the core of Takt, not bolted on afterwards. No cookie, no persistent identifier, no personal data leaves the browser. Visitors have nothing to accept.

When an event is not sent

Before any send, Takt checks four conditions in this order. If any is true, the event is silently dropped:

1. Visitor opt-out

The visitor declined measurement: localStorage contains takt_ignore = '1'.

2. Do Not Track & GPC

The browser signals Do Not Track (DNT header) or Global Privacy Control (Sec-GPC header / navigator.globalPrivacyControl) and respectDnt is on (default; turn it off with data-respect-dnt="false" or respectDnt: false).

3. Localhost / private IP

The host is local or on a private network (localhost, ::1, 0.0.0.0, *.local, 127.*, 10.*, 192.168.*, 172.16–31.*) and excludeLocalhost is on (default).

4. Sampling

sampleRate (or data-sample-rate) is below 1 and this event falls outside the kept fraction.
The opt-out, DNT/GPC and localhost guardrails are on by default. In local development, nothing is reported: that's intentional, not a bug.

With the npm SDK and framework wrappers, exclude adds another lever: any page or named event whose path starts with an excluded prefix (e.g. ['/app','/account'], segment-bounded so /app matches /app and /app/… but not /application) is never tracked. It’s checked at send time, so it holds across SPA navigation. The minimal CDN snippet omits this option.

Giving control to the visitor

You can expose a “Don’t track me” button that drives the opt-out. The choice is written to localStorage (key takt_ignore), so it persists across visits on this browser.

In an npm integration, use the dedicated functions:

import { optOut, optIn, isOptedOut } from '@vskstudio/takt-core'

optOut()     // sets takt_ignore='1', no event is sent anymore
optIn()      // removes the flag, resumes tracking
isOptedOut() // true while the visitor refuses measurement

These functions don’t need an instance to exist: a consent banner shown before init() can record the refusal, and every instance created later honours it. The framework wrappers re-export all three.

On the snippet (CDN) side, the global window.takt only exposes track. So drive the opt-out directly via localStorage — the snippet reads this key before each send:

function toggleTracking() {
  if (localStorage.getItem('takt_ignore') === '1') {
    localStorage.removeItem('takt_ignore') // opt-in
  } else {
    localStorage.setItem('takt_ignore', '1') // opt-out
  }
}

URL scrubbing

By default, Takt strips the query string and hash from every URL before sending — the page URL, the referrer and outbound link destinations keep only origin + path. A token, email or identifier slipped into ?... or #... therefore never reaches analytics.

You can tune this: trackQuery keeps the whole query, queryParams keeps only an allowlist, and scrubUrl replaces the logic with your own function (npm). On the snippet, use data-track-query or data-query-params for the same effect.

scrubUrl applies to every URL that leaves the browser: the page URL, the referrer, and since @vskstudio/takt-core 0.9.0 the url property of the Outbound Link: Click and File Download events sent by autocapture. Those links already arrive reduced to origin + path, so your function mostly serves to mask an identifier sitting in the path:

import { init } from '@vskstudio/takt-core'

const maskUserIds = (url: string) => url.replace(/\/users\/[^/]+/, '/users/:id')

init({ domain: 'example.com', outbound: true, files: true, scrubUrl: maskUserIds })

Route redaction

URL scrubbing removes the query and the hash, but the path is sent as is. A route that carries a secret in a segment therefore leaks it: /verify/abc123, /reset/7f3c9a or /invoices/4812.pdf reach the analytics with their token. Since @vskstudio/takt-core 0.10.0 (framework wrappers 0.8.0), two options replace those paths with the route pattern before sending. The real path never leaves the browser.

redactRoutes: redact the sensitive routes

List the routes that carry a secret. A path matching one of the patterns is sent as the pattern, every other path keeps its real value:

import { init } from '@vskstudio/takt-core'

init({
  domain: 'example.com',
  redactRoutes: ['/verify/[token]', '/reset/:code', '/invoices/[id].pdf']
})

/verify/abc123 is then sent as /verify/[token], while /blog/my-post stays /blog/my-post. Per-page stats stay intact everywhere else.

  • Pattern syntax: the SvelteKit, Astro and Next one ([param], [[optional]], [...rest], (group) segments ignored) and the Vue Router, React Router, Angular and Solid one (:param, :param?, *, **). A segment can mix text and a parameter ([id].pdf).
  • Scope: the page URL, the referrer when it comes from the same site, outbound and download link destinations, and the path of 404 events.
  • The pattern that is sent is normalized: groups are dropped (/(app)/verify/[token] becomes /verify/[token]) and so are parameter constraints (:id(\d+) becomes :id).

routeTemplates: send every page as a template

routeTemplates: true sends every page as its route template: /blog/hello becomes /blog/[slug], /users/42 becomes /users/:id. No real path is sent.

The core does not know your router, so it needs a routeTemplate resolver returning the current template. The framework wrappers wire it for you (see below). When the resolver returns nothing (no matched route), redactRoutes still applies and the real path is sent otherwise. In this mode, same-site referrers are reduced to the site origin, since their template is unknown.

The trade-off: per-page stats

SettingWhat is sentPages report
DefaultReal path, without query or hashOne row per URL
redactRoutesPattern for the listed routes, real path elsewhereIntact, except the redacted routes grouped together
routeTemplatesTemplate for every pageOne row per route: every article of a blog is merged

routeTemplates fits a private app (dashboard, customer area) where no path is worth reading as is. On a public site, it merges all your articles into a single row: prefer redactRoutes and list only the routes that carry a secret.

Route redaction runs in the browser, before sending: the real path is never stored. The dashboard's URL grouping only changes how paths already received are displayed.

Where to set it

IntegrationHow
@vskstudio/takt-coreredactRoutes, routeTemplates and routeTemplate options of init() / createTakt()
SvelteKitrouteTemplate read from page.route.id
Vuerouter option of the plugin or the component, or vueRouterTemplate(router)
ReactreactRouterTemplate(router) with a data router; redactRoutes recommended for Next.js
SolidsolidRouterTemplate(useCurrentMatches())
AngularrouteTemplateFromSnapshot() in the injection context of provideTakt
AstroAstro.routePattern, passed on by <TaktRoute />
Node (server)redactRoutes and a per-call route option
<takt-analytics> elementredact-routes attribute (comma-separated list), no routeTemplates
PHP (takt-core-php 0.6.0)redactRoutes, routeTemplates and routeTemplate of Options in Mode::Sdk; redactRoutes, route and withRoute() of the Takt server client
Laravel (0.6.0)TAKT_REDACT_ROUTES and TAKT_ROUTE_TEMPLATES, template read from the Laravel route
Symfony (0.6.0)redact_routes and route_templates nodes, path read from the Symfony route

On the PHP side, patterns also accept the Laravel and Symfony {param} syntax: see the PHP, Laravel and Symfony pages. The WordPress plugin does not expose these options.

The no-build snippet (/takt.js, 1 kB) does not support these options. To redact routes, use the npm package or a framework wrapper.

Deleting your account (GDPR)

You can delete your account yourself, without contacting support. This is your right to erasure (GDPR, Article 17).

Where: Settings → Profile tab → danger zone → “Delete my account” button.

The flow

  1. The button opens a confirmation dialog.
  2. Takt sends you an email containing a secure link. No password is required: confirmation happens through that link only.
  3. You confirm by clicking the emailed link. The account is then deactivated immediately.
Confirmation always goes through email. If nothing arrives, check your spam folder — nothing is deleted until the link is opened.

90-day grace period

Deactivation is reversible for 90 days. During this window the account is not erased: simply logging back in reactivates it — along with the organizations where you are the only member.

After that period, deletion becomes permanent and irreversible: account, personal data, avatar and the audience statistics of your solo organizations’ sites are purged with no way back.

Blocked case: sole owner of a multi-member organization

If you are the sole owner of an organization that has other members, deletion is blocked. You must first transfer ownership of that organization to another member (from the Team section) before you can delete your account.

Organizations where you are the only member are deleted together with your account.

What Takt does not collect

  • No cookie and no session-identifier storage.
  • No IP address kept on the client, no browser fingerprinting.
  • No query string or hash in URLs (stripped by default).
  • No personal data: keep your props anonymous and low-cardinality.
Because no personal data is processed, Takt integrates without a consent banner in most jurisdictions. Always check your own legal context.