Install
One async script tag, once, in a shared layout. The hard part is not the tag — it is putting it somewhere that renders on every page you care about and nowhere you do not.
Add a site in the dashboard, list the domains the key may be used from, then paste this
before </body>:
<script async src="https://isotherm.app/isotherm.js" data-site="pk_live_xxx"></script>
The key is not a secret. It is visible in the page source of every tracked page by design. What actually authorizes recording is the Origin allowlist: a browser cannot forge the Origin header, so a key copied out of your HTML is useless on any other domain. Do not put it in an environment variable.
Where it goes
Laravel (Blade)
The shared layout, usually resources/views/layouts/app.blade.php. If the
project has several layouts — an auth screen, a marketing page — add it only to the ones
whose pages you actually want recorded. Put it in a @stack only if you are
certain that stack is flushed on every page: a stack nobody pushes to produces no tag and
no error.
Filament
Filament owns its own layout, so register a render hook in a service provider instead:
use Filament\Support\Facades\FilamentView;
use Filament\View\PanelsRenderHook;
FilamentView::registerRenderHook(
PanelsRenderHook::BODY_END,
fn (): string => Blade::render('<script async src="..." data-site="..."></script>'),
);
BODY_END, not HEAD_END — the tracker must load after the DOM exists.
Nova
Nova is a SPA, so a Blade tag would only run on the first load. Register it from
NovaServiceProvider::boot() with Nova::script(). Client-side
routing is already handled: the SDK patches history.pushState and files each
route as its own page.
Livewire and Inertia
Nothing special. Both swap the DOM without a page load; the SDK listens on the document in the capture phase and patches the history API, so it keeps seeing clicks on elements that were replaced.
One caveat worth knowing on Livewire-heavy pages: constant DOM updates suppress dead-click detection, because Isotherm treats "the DOM changed after the click" as evidence the click did something. You will see fewer dead clicks. That is a deliberate bias toward false negatives — a missed dead click costs nothing, a false one sends someone hunting a bug that does not exist.
WordPress
Do not paste it into a theme template; a theme update will erase it. Enqueue it from a child theme or a one-file mu-plugin, in the footer.
Verify it is actually recording
Do not trust a green checkmark anywhere, including your own:
- Load a tracked page and confirm
isotherm.jsreturns 200 in the Network tab. - Click something, wait about six seconds, and confirm a
POSTto/api/v1/collectreturns 202. - A 403 means the page's origin is not on that site's allowlist. Add the domain in the dashboard — do not change the key.
Pinning a version
/isotherm.js is the floating latest, cached for five minutes, so a fix
reaches every site without anyone redeploying. If you would rather not have a script change
underneath you, pin the immutable build — it is byte-identical forever and cached for a year:
<script async src="https://isotherm.app/isotherm-0.2.0.js" data-site="pk_live_xxx"></script>
There is also /isotherm.debug.js — the same code unminified, with a source
map, for when a stack trace into S(e,t) is not helping anyone.
Options
All optional, set as data-* attributes on the tag.
| Attribute | Default | What it does |
|---|---|---|
data-site | — | Required. The site's public key. |
data-move | 1 | Record mouse movement. 0 cuts payloads sharply. |
data-sample | 1 | Fraction of sessions to record. Decided once per session, so a recorded visitor stays recorded for the whole visit. |
data-grid | 16 | Movement quantization in px. Bigger is smaller and coarser. |
data-strip-query | 0 | Treat ?page=2 and ?page=3 as one page. |
data-text | 1 | Capture text of clicked elements, and keep text in page copies. See Privacy. |
data-dnt | 1 | Honour navigator.doNotTrack. |
data-debug | 0 | Log to the console. |