Installing the script

Developer

Coming soon — not open for wide use yet. Installing the tracking script on your own website isn't generally available. Tracking currently runs only on pages and forms published from the app, where it's included automatically and there is nothing to install. Keep this page for when the feature opens up; get in touch if you'd like early access.

This page is for whoever manages your website. It's the one place in this documentation with code in it. If that's not you, send them this link — they won't need anything else.

The snippet

Add this to every page you want tracked, ideally in the <head>:

<script defer
        src="https://your-tracking-host/s/thoughtfuly.js"
        data-site-key="YOUR_SITE_KEY"></script>

Your site key and tracking host are provisioned for your account rather than being self-service — ask your account administrator for them if you don't already have them. The site key is not a secret: it sits in your page source, and it only identifies which account the data belongs to.

That's the whole installation. Page views start immediately.

If your site is a web artifact published from this platform, the script is already there. Nothing to do.

Options

All configuration is done with attributes on the script tag.

AttributeDefaultWhat it does
data-site-keyRequired. Identifies your account.
data-require-consentfalseWait for consent before tracking anything. Set this if you use your own cookie banner.
data-consent-uioffbanner or modal to show the built-in consent prompt. Implies data-require-consent.
data-capture-on-changefalseCapture an email as it's typed, not only on submit. See the warning below.
data-cookie-domainautoOverride the cookie domain. Only needed for unusual setups.

Without it, tracking starts on page load (do-not-track is still honoured — that can't be switched off). With it, nothing is stored or sent until something calls thoughtfuly.consent.grant().

⚠️ If you use your own cookie banner, you must set this. Otherwise the script tracks immediately and your banner's "decline" arrives too late to have prevented anything. Setting data-consent-ui turns it on for you, since showing a banner and tracking anyway would be incoherent.

About data-capture-on-change

Off by default, on purpose. Turning it on captures an email address as soon as the visitor types a valid one — including addresses they then delete and never submit. That is a meaningfully different privacy proposition from capturing on submit, and in several jurisdictions a legally different one. Turn it on knowingly, or leave it alone.

The script works out the right cookie domain automatically so that example.com, www.example.com and shop.example.com share one visitor. Set this only if that detection gets it wrong — for example on an internal hostname with an unusual suffix. Use a leading dot: data-cookie-domain=".example.com".

The JavaScript API

The script exposes a single global, thoughtfuly. Nothing else is added to window.

thoughtfuly.page()                 // send a page view manually
thoughtfuly.track(name, props)     // send a custom event

thoughtfuly.consent.status()       // "granted" | "denied" | "unknown"
thoughtfuly.consent.grant()        // allow tracking; flushes anything queued
thoughtfuly.consent.deny()         // stop tracking; clears stored identifiers
thoughtfuly.consent.reset()        // back to undecided
thoughtfuly.consent.onChange(fn)   // called whenever the state changes

Custom events

thoughtfuly.track("signup_started", { plan: "pro" });

Event names must be 1–64 characters, letters, numbers, underscore or hyphen — no spaces or dots. Invalid names are rejected with a console warning rather than silently dropped, so you'll notice during development.

Single-page apps

Route changes are detected automatically, including pushState navigation, and a page view is sent when the path changes. Query-string-only changes don't fire one.

If your router does something unusual and views aren't appearing, call thoughtfuly.page() yourself after each navigation.

If you already run a cookie banner, wire it to these two calls. The pattern is the same whatever product you use:

// When the visitor accepts your analytics category:
thoughtfuly.consent.grant();

// When they decline, or withdraw a previous acceptance:
thoughtfuly.consent.deny();

A typical integration, using whatever event your banner emits:

myConsentBanner.on("change", (categories) => {
  categories.analytics
    ? thoughtfuly.consent.grant()
    : thoughtfuly.consent.deny();
});

Until grant() is called, nothing is stored and nothing is sent — page views that occur while the visitor is deciding are held in memory and flushed on grant(), or discarded on deny(). Setting data-consent-ui is unnecessary if you're doing this; use one or the other, not both.

See Consent for the behaviour in full.

Things worth knowing

It won't break your site. Every entry point is wrapped. If the tracking host is unreachable, slow, or returns errors, the script fails quietly and your page carries on. It never throws into your code.

It adds one global. window.thoughtfuly, and nothing else. If that name already exists, the script logs a warning and does nothing rather than overwrite it.

Calls are safe before load. The API is available as soon as the script runs; you don't need to wait for a ready event.

Content Security Policy. If you run a CSP, allow your tracking host in both script-src (to load the script) and connect-src (to send data).

Checking it works

  1. Load a tracked page with your browser's network tab open.
  2. Look for a request to /t on your tracking host. It should return 200.
  3. Check cookies for the page — _tfy_vk should be set.
  4. Submit a form containing an email address; you should see a request to /i, and _tfy_ih should appear.
  5. Navigate to a subdomain of the same site — _tfy_vk should be unchanged.

If step 5 shows a different value, the cookie domain was detected incorrectly. Set data-cookie-domain explicitly.