npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@privatrak/tracker

v1.0.0

Published

Cookieless, privacy-first analytics tracker: auto-captures pageviews, clicks and form submissions with no cookies, no localStorage, no PII and no consent banner.

Readme

@privatrak/tracker

The tracking script behind Privatrak, a privacy-first product analytics service. Add it to a site and it starts recording pageviews, clicks and form submissions on its own — no manual instrumentation, no track() calls sprinkled through your components, no tagging plan to maintain. It sets no cookies, writes nothing to localStorage or sessionStorage, does no browser fingerprinting, and never reads the values a visitor typed into a form. Visitors are counted server-side from an irreversible daily hash of IP and User-Agent, so there is no identifier to store on the device, nothing to ask consent for, and no cookie banner to show. The browser bundle is a single ~3 KB IIFE.

Using Nuxt? Install @privatrak/nuxt instead — it wraps this package in a Nuxt module with a useTracker() composable.

Installation

npm install @privatrak/tracker

Or skip the bundler entirely and load the hosted script, which Privatrak serves for you:

<script src="https://api.privatrak.com/tracker.js" data-api-key="stk_your_project_key"></script>

The script tag route needs nothing built and nothing redeployed when a setting changes: the options below have data-* attributes too, and the API host is read from the script's own src. The rest of this README uses the npm form.

Quick start

import { init, track, setTraits } from '@privatrak/tracker';

init({
  apiKey: 'stk_your_project_key',
  apiHost: 'https://api.privatrak.com',
});

// Everything below is optional — autocapture already records pageviews,
// clicks and form submissions, including single-page-app navigations.

track('checkout-completed', { plan: 'pro' });
setTraits(['paid', 'admin']);

Call init() once, as early as you can — from your app's entry module, or a client-side bootstrap file. It is a no-op during server-side rendering, so it is safe to import from code that also runs on the server. Your project's API key is in the Privatrak dashboard under Settings → API Keys; it is a public, write-only key and is meant to ship in client code.

Elements can be labelled in markup instead of in code, which is how most feature-level analytics end up being defined:

<button data-track="signup-cta" data-track-plan="pro" data-track-source="header">
  Sign up
</button>

A click on that button is recorded with data_track: "signup-cta" and data_track_attrs: { plan: "pro", source: "header" }. If the click lands on something inside a labelled element, the tracker walks up the DOM to find it.

Configuration

Every option is optional except apiKey. Pass them to init(), or set the matching attribute on the hosted <script> tag. Anything you do not set follows your project's settings from the dashboard, which the tracker fetches at startup; values passed to init() always win over the server's.

| Option | Script attribute | Type | Default | Description | |--------|------------------|------|---------|-------------| | apiKey | data-api-key | string | — | Project API key. Required; without it nothing is sent. | | apiHost | data-api-host | string | the page's own origin (the hosted script uses the origin of its src) | Base URL of the Privatrak API, e.g. https://api.privatrak.com. | | autocapture | data-autocapture | boolean | true | Record clicks and form submissions automatically. | | autocapturePageviews | data-autocapture | boolean | true | Record pageviews, including SPA navigations. | | autocaptureElements | — | string[] | a, button, input[type=submit], plus ARIA roles such as [role=button], [role=menuitem], [role=tab] | Selectors treated as interactive click targets. | | walkDepth | data-walk-depth | number | 3 | How many ancestors to walk up when looking for an interactive element or a data-track label. | | maxTextLength | data-max-text-length | number | 100 | Element text is truncated to this many characters. | | flushIntervalMs | data-flush-interval | number | 5000 | How often the queue is sent. | | maxQueueSize | data-max-queue-size | number | 10 | Queue length that triggers an immediate send. | | piiUrlParams | data-pii-params | string[] | ['email', 'token', 'key', 'password', 'secret'] | Query parameters whose values are replaced with :redacted before the URL is sent. | | piiPathPatterns | data-pii-path-patterns | string[] | [] | Regular expressions whose matching path segments are replaced with :redacted. | | normalizePathIds | data-normalize-path-ids | boolean | true | Collapse ID-shaped path segments to :id, so /orders/4711 and /orders/4712 are one page. | | excludedUrls | data-excluded-urls | string[] | [] | URL patterns that are not tracked at all. | | samplingRate | data-sampling-rate | number | 1.0 | Fraction of visitors recorded, from 0 to 1. | | traits | data-traits | string[] | [] | Session labels attached to every event in the batch — see setTraits(). |

List options are comma-separated in an attribute: data-pii-params="email,token". The two autocapture switches share one attribute, which takes a list of what to keep: data-autocapture="pageviews,clicks", or data-autocapture="none" to record nothing automatically. Add data-manual-init to stop the script initialising itself, and call window.tracker.init({ ... }) when you are ready.

Project settings, and what happens if they cannot be loaded

Settings come from three places, applied in this order: the values built into the script, your project's settings fetched from apiHost, and the data- attributes on the script tag, which win over both.

Nothing is sent until that fetch has succeeded. The project's settings carry its URL redaction rules, so that one request is what stands between a URL in the page and a URL in the database. It is retried five times — 500 ms, 1 s, 2 s and 4 s apart — on network errors, timeouts, 408, 429 and any 5xx. A 401 or 403 is not retried: the key is wrong, or the page's origin is not among the project's allowed domains, and the answer will not change however often it is asked.

If the settings never arrive, the page load sends nothing at all, and one console.warn names the request that failed and says why. Sending unredacted URLs would be worse than sending none. The Tracker Reference goes through the retry rules and the queue behaviour in full.

The Element Picker

Privatrak's point-and-click element picker is a second file, tracker-picker.js, and it is not part of this package — your visitors never download it. It is fetched from apiHost the first time someone signed in to the Privatrak dashboard activates the picker on your site, which is also the only time it can run. If that fetch fails — no apiHost was configured, or the API is not reachable from the page — the tracker logs a warning and carries on tracking; nothing else is affected.

API

init(config?): void

Starts the tracker: applies the configuration, records the first pageview, and attaches the delegated click and submit listeners plus the History API patches that detect SPA navigation. Calling it twice logs a warning and does nothing the second time. Returns immediately; the first batch leaves once the project's settings have loaded.

track(name, attrs?): void

Records a custom event. name is the feature label you will see in the dashboard, attrs an optional flat object of string values, equivalent to data-track-* attributes in markup.

track('invite-sent', { role: 'editor' });

setTraits(traits): void

Labels the current session with developer-chosen strings — ['paid', 'trial'], ['plan:pro'], a feature-flag bucket — which every subsequent event carries and which the dashboard can filter and break down by. Traits are your own labels, not personal data: at most 50 per batch, 64 characters each. Call it whenever what you know about the session changes.

version: string

The package version of the build you imported, baked in at build time. On the hosted script the same value is at window.tracker.version. Quote it in a bug report.

Types

TrackerInitConfig (the shape init() takes) and DataTrackAttrs are exported as types.

What gets captured

Pageview — on load and on every SPA navigation (pushState, replaceState, popstate). Navigations to the same URL are not recorded twice. Fields: page_url, referrer, timestamp.

Click — on anything in autocaptureElements, or anything carrying data-track, via a single delegated listener on document. A click on a non-interactive child walks up to walkDepth ancestors to find the real target. Fields: element_tag, element_id, element_class, element_text (truncated), data_track, data_track_attrs, plus page_url and timestamp.

Form submit — on submit, with the form's own identity and labels. Field values are never read. Fields: element_tag, element_id, element_class, data_track, data_track_attrs, page_url, timestamp.

Custom — whatever you pass to track().

Password fields, hidden fields and inputs whose autocomplete starts with cc- are skipped outright, so they are never even click targets.

Delivery

Events are queued in memory and sent in batches to POST /api/events — every 5 seconds, or as soon as 10 are waiting, or immediately when the tab is hidden. Normal sends use fetch() with keepalive; the tab-hidden send uses navigator.sendBeacon() so it survives the page going away. A failed batch is re-queued for the next cycle, and the queue is capped at 100 events so a broken connection cannot grow it without bound.

URLs

The tracker sends one absolute URL per event and the API splits it into origin, path, query and fragment. Origin plus path is the page's identity, so /search?q=analytics and /search?q=funnels are the same page, counted once, while the parameters stay individually filterable. Redaction happens here in the browser, before anything is sent: piiUrlParams, piiPathPatterns and normalizePathIds rewrite the URL first.

Privacy

This is the whole point of the package, so it is worth being specific.

  • No cookies and no storage. Nothing is written to document.cookie, localStorage or sessionStorage while tracking. There is no device identifier, which is why no consent banner is required for it.
  • No fingerprinting. No canvas, WebGL or AudioContext probing, no navigator enumeration.
  • Visitors are counted server-side. The API derives a session ID from HMAC(IP + User-Agent + project, daily key) and stores only that hash — raw IP and User-Agent are never written down. The key rotates at midnight in the project's timezone, so yesterday's sessions cannot be linked to today's. It is the model Plausible Analytics uses, which CNIL has found acceptable without consent.
  • Form values are never captured. Not opt-in, not redacted afterwards — the tracker does not read them.
  • Element text is truncated to maxTextLength (100) characters, and sensitive inputs are excluded from tracking entirely.
  • URLs are cleaned in the browser, before the event leaves the page.

More detail, and the data processing terms, are at privatrak.com/privacy.

Links

Versioning and license

This package is versioned independently of the hosted product. Changes are recorded in CHANGELOG.md, shipped in the package. Released under the MIT license (LICENSE): it runs on customers' own sites, so it is licensed separately from the rest of the service.