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 addQuickstart
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
| Call | Notes |
|---|---|
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 loadFlags 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
| Option | Default | |
|---|---|---|
apiHost | https://<region>.i.analytics.atrix.dev | A 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. |
cookieless | false | Uses no storage at all and tags events consent: "cookieless". |
consent | "granted" | The starting state before the visitor decides. A stored decision wins. |
capturePageview | true | $pageview on load and on SPA navigation. |
autocapture | false | true 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 / flushIntervalMs | 20 / 30 000 | Batching. The queue is also sent with sendBeacon when the page is hidden. |
maxQueueSize / maxQueueBytes | 1000 / 5 MB | Past this the oldest events are dropped and reported as $dropped_events. |
requestTimeoutMs, sessionIdleTimeoutMs | 10 s, 30 min | |
compression | true | Gzip request bodies. |
replay | off | true or { sampleRate, minDurationMs, triggers, maskAllInputs, maskAllText, captureNetwork, captureConsole }. See Session replay. |
debug | false | Log 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,phoneorotpbecome[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.
pendingkeeps events in memory, sends nothing and writes nothing.grantedsends what was held (taggedconsent: "pending") and starts persisting.deniednever 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_referrerand$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");