atrixANALYTICS
SDKs

Web

@atrix.dev/analytics-web: events, pageviews, identity, consent, flags and session replay in 7.7 KB.

The browser SDK is 7.7 KB gzipped with its core. It never throws, does nothing until it is configured, and can run without cookies. The session-replay recorder is a separate chunk that is only downloaded when a session is actually recorded.

npm i @atrix.dev/analytics-web      # or: bun add / pnpm add / yarn add

Quickstart

import * as atrix from "@atrix.dev/analytics-web";

// The region comes from the key: this one sends to eu.i.analytics.atrix.dev.
atrix.init("atx_pk_eu_production_XXXXXXXXXXXXXXXXXXXXXX");

atrix.track("checkout_completed", { amount: 1999, currency: "AED", booking_id: "bk_42" });
atrix.identify("user_42", { plan: "pro" });            // your user id, never an email or phone number
atrix.group("company", "acme", { name: "Acme" });
atrix.register({ environment: "production" });         // stamped on every later event, persisted
atrix.reset();                                         // on sign-out

$pageview is recorded when the page loads and on every single-page-app navigation (pushState, replaceState and popstate). If you turn that off with capturePageview: false, call atrix.page() yourself.

API

CallNotes
init(publicKey, options?)Starts the default client and returns it. Calling it again replaces the client. On the server (SSR) and with an invalid key it is a no-op.
createAtrix(publicKey, options?)A separate client, for several projects on one page.
track(event, properties?, { $set?, $set_once?, timestamp? }?)Names starting with $ are reserved for system events.
page(properties?)A manual $pageview.
identify(distinctId, $set?, $set_once?)One-way. Once identified, a different id is ignored until reset().
setRevenueCustomer(customerId, { provider? }?)Links a billing customer to the current user; see Revenue.
reset()A new anonymous id and session; clears super-properties and groups.
group(type, key, properties?)Up to 5 group types. Later events carry groups.
register(props) / registerOnce(props) / unregister(key)Persisted super-properties.
setConsent("granted" | "pending" | "denied"), optIn(), optOut(), getConsent()See Privacy below.
flush(), shutdown()Both return promises that never reject.
getDistinctId(), getInstance()
startReplay(), stopReplay()Switch session replay at runtime.

Feature flags

import * as atrix from "@atrix.dev/analytics-web";

atrix.init("atx_pk_eu_production_XXXXXXXXXXXXXXXXXXXXXX", {
  bootstrap: { flags: { "new-checkout": true } },   // known values before the first load
  preloadFlags: true,
});

if (atrix.isEnabled("new-checkout")) {
  // …
}
const variant = atrix.getVariant("pricing");   // "test" | "control" | null
const payload = atrix.getPayload("pricing");
const stop = atrix.onFlags((flags) => console.log(flags));   // now, if known, and after every load

Flags load lazily on first use, are cached next to the identity, and reload on identify, group and reset. Each read sends one $feature_flag_called per flag and value per session, which is what experiment exposure is computed from. Set sendFeatureFlagEvents: false to turn that off.

Options

OptionDefault
apiHosthttps://<region>.i.analytics.atrix.devA first-party proxy such as https://example.com/_atx. The SDK posts to {apiHost}/e.
persistence"localStorage""cookie" keeps identity in a first-party cookie (set cookieDomain: ".example.com" to share it across subdomains). "memory" keeps everything in memory.
cookielessfalseUses no storage at all and tags events consent: "cookieless".
consent"granted"The starting state before the visitor decides. A stored decision wins.
capturePageviewtrue$pageview on load and on SPA navigation.
autocapturefalsetrue records clicks on links and buttons as $autocapture (tag and link path only). { captureText: true } adds element text.
beforeSend(event)Return the event (edited if you like) or null to drop it. A hook that throws drops the event.
propertyDenylist[]Keys removed from properties, $set and $set_once, after beforeSend.
superProperties{}Stamped on every event for the life of the page. Not persisted; register() is.
flushAt / flushIntervalMs20 / 30 000Batching. The queue is also sent with sendBeacon when the page is hidden.
maxQueueSize / maxQueueBytes1000 / 5 MBPast this the oldest events are dropped and reported as $dropped_events.
requestTimeoutMs, sessionIdleTimeoutMs10 s, 30 min
compressiontrueGzip request bodies.
replayofftrue or { sampleRate, minDurationMs, triggers, maskAllInputs, maskAllText, captureNetwork, captureConsole }. See Session replay.
debugfalseLog problems to the console.
import * as atrix from "@atrix.dev/analytics-web";

atrix.init("atx_pk_eu_production_XXXXXXXXXXXXXXXXXXXXXX", {
  apiHost: "https://example.com/_atx",
  persistence: "cookie",
  cookieDomain: ".example.com",
  consent: "pending",
  autocapture: { captureText: false },
  propertyDenylist: ["email"],
  beforeSend: (event) => (event.event === "debug_ping" ? null : event),
});

// Later, from your consent banner:
atrix.setConsent("granted");

Privacy defaults

  • No IP addresses are stored. Ingest resolves the country and city, then discards the address.
  • URLs are cleaned before they leave the page. Values of query parameters named like token, secret, password, auth, key, code, session, sig, email, phone or otp become [redacted]. UTM parameters are kept.
  • Autocapture is off. When on, it records no element text unless you ask, skips anything inside [data-atx-no-capture], and never reads form inputs.
  • Consent. pending keeps events in memory, sends nothing and writes nothing. granted sends what was held (tagged consent: "pending") and starts persisting. denied never touches the network and deletes the stored identity and queue; only the decision itself is kept.
  • Cookieless mode reads and writes no cookies, localStorage or sessionStorage.
  • On the first visit, first-touch attribution is sent once as $set_once: $initial_utm_*, $initial_referrer and $initial_current_url.

Delivery

Batches are gzipped and sent as text/plain with ?compression=gzip-js, which makes them a CORS "simple request" with no preflight. When the page is hidden (visibilitychange or pagehide) the queue goes out through navigator.sendBeacon. Events that fail wait in localStorage and are retried with backoff, including after a reload.

Several projects on one page

import { createAtrix } from "@atrix.dev/analytics-web";

const marketing = createAtrix("atx_pk_eu_production_AAAAAAAAAAAAAAAAAAAAAA");
const product = createAtrix("atx_pk_eu_production_BBBBBBBBBBBBBBBBBBBBBB");

marketing.track("newsletter_signup");
product.track("project_created");

On this page