Install
JavaScript SDK
The npm package for web apps, React Native, Electron and Node servers.
@anyanalytics/analytics is one package for every JavaScript runtime: browsers, React Native, Electron and Node servers. It batches events, keeps them while the device is offline, retries with backoff, and a retried event is never counted twice. It ships with TypeScript types.
npm install @anyanalytics/analyticsWeb apps
For React, Next.js, Vue, Svelte or any app with a build step. Import it once on the client: main.tsx (Vite/React), app/layout.tsx in a client component (Next.js), or main.ts (Vue, Svelte).
import { analytics } from "@anyanalytics/analytics";
analytics.init({
writeKey: "vxw_YOUR_WRITE_KEY",
host: "https://api.anyanalytics.org",
capturePageviews: true, // page loads + client-side navigations
});With capturePageviews on, the SDK records a $pageview on load and on every client-side navigation. Add captureOutboundClicks: true to record clicks on links to other sites as $outbound_click. Both are off by default in the npm package (the script tag turns them on).
Note
Recorded page URLs drop the fragment and every query parameter except utm_* and ref, since they often hold tokens or personal data. List any others you want to keep in urlQueryParams.
Custom routers
Pageview capture listens to history.pushState and the back button, which covers the common routers. If yours works differently, leave capturePageviews off and call analytics.page() after each navigation.
Across subdomains
In browsers the visitor's anonymous id and session are kept in a first-party cookie on your parent domain, so shop.example.com and blog.example.com count one visitor with one session. The domain is detected; pass cookieDomain: "example.com" to pin it, or cookieDomain: false to keep ids per subdomain in storage. Unsent events always stay in storage.
Without storing anything
Pass cookieless: true and the SDK writes nothing to the visitor's browser. Visitors are then counted per day, and events queued while offline are lost on reload. See Cookieless mode.
Track and identify
Import the shared analytics instance anywhere and track what people do:
import { analytics } from "@anyanalytics/analytics";
analytics.track("signup", { plan: "pro", source: "pricing_page" });Once someone signs in, identify ties their events to your user id. Their earlier anonymous events are linked to the same person. Traits are saved on their profile.
// After login: future events belong to this user
analytics.identify("user_123", { email: "[email protected]", plan: "pro" });
// On logout: start a fresh anonymous visitor
analytics.reset();React Native
React Native has no localStorage, so pass AsyncStorage as the storage option. It keeps the visitor id, the session and unsent events between launches.
npm install @anyanalytics/analytics @react-native-async-storage/async-storageimport AsyncStorage from "@react-native-async-storage/async-storage";
import { analytics } from "@anyanalytics/analytics";
analytics.init({
writeKey: "vxw_YOUR_WRITE_KEY",
host: "https://api.anyanalytics.org",
// Persist the visitor id and offline queue between launches
storage: AsyncStorage,
context: { platform: "react-native", app_version: "1.0.0" },
});
analytics.screen("Home");Import this file once from App.tsx (or index.js) so it runs on startup.
- Call
analytics.screen(name)from your navigation listener to record screen views as$screenevents. - Events are sent every 10 seconds, or as soon as 20 are queued. To send right away when the app goes to the background, call
analytics.flush()from anAppStatelistener.
Any object with getItem, setItem and removeItem works as storage. They may return values or promises.
Electron and desktop apps
In Electron or Tauri, initialize the SDK in the renderer, where it stores its state in localStorage. Import it from your renderer entry (Electron) or main frontend file (Tauri) so it runs once.
import { analytics } from "@anyanalytics/analytics";
analytics.init({
writeKey: "vxw_YOUR_WRITE_KEY",
host: "https://api.anyanalytics.org",
context: { platform: "desktop", app_version: "1.0.0" },
});
analytics.screen("Main window");Events are queued while offline and sent when the connection comes back.
Node servers
On a server, one process handles many users at once. Create one instance and pass the user on every call.
import { Analytics, memoryStorage } from "@anyanalytics/analytics";
export const analytics = new Analytics().init({
writeKey: "vxw_YOUR_WRITE_KEY",
host: "https://api.anyanalytics.org",
storage: memoryStorage(),
context: { platform: "server", app_version: process.env.npm_package_version },
});
// Send whatever is still queued before the process exits
process.on("SIGTERM", () => void analytics.shutdown());
// On a server, call analytics.capture(event, { distinctId }) with the user of
// each request. Never set a process-wide identity: requests run concurrently.capture(event, options) takes the identity per call, so concurrent requests can't mix up users:
import { analytics } from "./analytics";
// distinctId: the user (or anonymous visitor id) this request is for
analytics.capture("order_completed", {
distinctId: user.id,
properties: { order_id: "ord_123", total: 49.9 },
});| Option | What it is |
|---|---|
distinctId | Required. Your user id, or an anonymous id you keep. |
properties | Event properties. |
sessionId | Optional. Groups events into a session you manage. capture() never starts sessions itself. |
timestamp | Optional. A Date or epoch milliseconds. Defaults to now. |
To set traits on a user from the server, capture a $identify event with $set:
// Set traits on the user making the request (no shared identity state)
analytics.capture("$identify", {
distinctId: user.id,
properties: { $set: { email: user.email, plan: user.plan } },
});Heads up
Don't call identify() or track() on a server. They keep one identity for the whole process, so with concurrent requests one user's events would be attributed to another.
- Events are sent in the background. In short-lived scripts and serverless functions,
await analytics.flush()before returning. - Server events with
platformset to"server"or"node"don't get a location from the request, since it comes from your data center, not the user.
API
| Method | What it does |
|---|---|
init(options) | Starts the SDK. Call it once; later calls are ignored. |
identify(userId, traits?) | Ties this device's events to a user id and saves the traits on their profile. |
track(event, properties?) | Records a custom event. |
page(properties?) | Records a $pageview for the current URL (browsers). |
screen(name, properties?) | Records a $screen event (apps and games). |
capture(event, options) | Records an event for the user you pass. Use it on servers. |
setContext(context) | Merges fields into the context sent with every event, such as app_version once it's known. |
reset() | Forgets the user (on logout): new anonymous id, new session. |
flush() | Sends everything queued now. Returns a promise. |
shutdown({ timeoutMs? }) | Stops timers and sends what it can (waits up to 5 s by default). Resolves to { pending }, the number of events left unsent. |
distinctId | The current user id, or the anonymous id before identify(). |
sessionId | The current session's id. Pass it with distinctId to server-side flows, such as checkout metadata, so payments are attributed to the visit. See Revenue. |
Sessions are handled for you: a new one starts after 30 minutes without events, and a $session_start event is recorded each time. Call init() before anything else.
Need more than one project in the same app? Create separate instances with new Analytics().init({ … }).
Options
| Option | Default | What it does |
|---|---|---|
writeKey | Your project's write key. Required. | |
host | The ingestion address, https://api.anyanalytics.org. Required unless you set endpoint. | |
endpoint | {host}/v1/batch | The full URL events are posted to, such as a path on your own domain. Relative URLs resolve against the page. See Ad blockers and proxies. |
capturePageviews | false | Browsers: record $pageview on load and on client-side navigation. |
captureOutboundClicks | false | Browsers: record clicks on links to other sites as $outbound_click. |
urlQueryParams | [] | Query parameters to keep in recorded URLs, besides utm_* and ref. |
cookieless | false | Store nothing on the device. Visitors are counted per day. |
cookieDomain | detected | Browsers: the parent domain whose subdomains share the visitor and session, or false to keep them per subdomain. |
storage | localStorage, or memory | Where the visitor id, session and unsent events are kept. |
context | Fields sent with every event: platform, app_version, app_build, os, device and so on. | |
flushAt | 20 | Send as soon as this many events are queued. |
flushIntervalMs | 10000 | Send at least this often. |
maxBatchSize | 100 | Events per request (at most 500). |
maxQueueSize | 1000 | Unsent events kept. The oldest are dropped beyond this. |
sessionTimeoutMs | 1800000 | Inactivity after which a new session starts (30 minutes). |
flushOnHide | true | Browsers: send queued events when the tab is hidden or closed. |
fetch | global fetch | Replace fetch, for tests or custom runtimes. |
debug | false | Log what the SDK does to the console. |
Delivery
- Unsent events survive reloads and restarts when
storagepersists (localStorage in browsers, AsyncStorage in React Native). On a server they're kept in memory, so callshutdown()before the process exits. - Failed requests are retried with backoff, honoring the server's
Retry-After. Every event has its own id, so a retried event is never counted twice. - Events the server rejects are logged to the console with the reason. You'll also see them in Live events.