Skip to content

vitonsky/plausible-client

Repository files navigation

Plausible client to collect analytics in browser with no hassle.

Why?

There are official plausible-tracker package, it have quite few code, but have a lot of bugs like

  • "enableAutoOutboundTracking breaks target="_blank" and probably noopener security" #12
  • uses XHR API that loses analytics on static sites with no SPA approach (like Astro does) #16
  • uses callbacks, that will not be called on localhost, that force users write code that works different locally and on production

Current package is lightweight too, but written and maintained by those who use a plausible analytics on production. See dogfooding.

Usage

Install package with npm i plausible-client

Create instance and play

import { Plausible } from 'plausible-client';

const plausible = new Plausible({
	apiHost: 'https://plausible.io',
	domain: 'example.org',
});

plausible.sendEvent('test', {
  props: {
    foo: 1,
    bar: 'string',
    baz: null,
  },
  revenue: {
    currency: 'USD',
    amount: 5,
  },
});

Automatically track pageviews

To track page views automatically, call enableAutoPageviews:

import { Plausible, enableAutoPageviews } from 'plausible-client';

const plausible = new Plausible({
  apiHost: 'https://plausible.io',
  domain: 'example.org',
});

// Function returns cleanup callback and starts track pageviews
enableAutoPageviews(plausible);

Automatically track outbound clicks

To track outbound clicks automatically (same-origin links are skipped automatically), call enableAutoOutboundTracking:

import { Plausible, enableAutoOutboundTracking } from 'plausible-client';

const plausible = new Plausible({
  apiHost: 'https://plausible.io',
  domain: 'example.org',
});

// Function returns cleanup callback and starts tracking outbound clicks
enableAutoOutboundTracking(plausible);

// Optionally capture link text or apply a custom filter
enableAutoOutboundTracking(plausible, {
  captureText: true,
  filter: (url, text) => !url.includes('internal'),
});

Track any link clicks

For fine-grained control over which links to track, use enableLinkClicksCapture directly:

import { Plausible, enableLinkClicksCapture } from 'plausible-client';

const plausible = new Plausible({
  apiHost: 'https://plausible.io',
  domain: 'example.org',
});

// Function returns cleanup callback
enableLinkClicksCapture(plausible, {
  eventName: 'Link click',   // default
  captureText: true,          // include link text as a prop
  filter: (url, text) => url.startsWith('https://'),
});

Score user sessions

To collect device/region signals and detect trivial bots, call enableSessionScoring:

import { Plausible, enableSessionScoring } from 'plausible-client';

const plausible = new Plausible({
  apiHost: 'https://plausible.io',
  domain: 'example.org',
});

// Sends a "Session scored" event on the next animation frame
enableSessionScoring(plausible);

The Session scored event includes these props:

Prop Description
botScore Numeric score — ≥ 3 indicates a likely bot
botSignals Comma-separated triggered signals, e.g. webdriver,headless_ua
sessionAge Seconds since the first recorded visit
timeZone IANA timezone resolved from Intl.DateTimeFormat
language navigator.language
languages navigator.languages joined with ,
screenSize {width}x{height} from window.screen
hardwareConcurrency Number of logical CPU cores
deviceMemory Device RAM in GB (where available)
devicePixelRatio Screen pixel density

You can customise the storage backend or key used to persist the first-visit timestamp:

import { Plausible, enableSessionScoring, CookieStorage } from 'plausible-client';

enableSessionScoring(plausible, {
  storage: new CookieStorage(),
  firstVisitKey: 'my_first_visit',
});

You can also use getBotSignals() independently:

import { getBotSignals } from 'plausible-client';

const { isBot, score, signals } = getBotSignals();
// isBot: boolean, score: number, signals: string[]

Track page engagement

To automatically track user engagement — scroll depth and active time on the page — call enableEngagementTracking:

import { Plausible, enableEngagementTracking } from 'plausible-client';

const plausible = new Plausible({
  apiHost: 'https://plausible.io',
  domain: 'example.org',
});

// Function returns a cleanup callback and starts tracking engagement
enableEngagementTracking(plausible);

The tracker sends a Page engagement event to Plausible. Tracking is only active while the browser tab is visible and the window is focused — time spent on a hidden or unfocused tab is excluded.

Events are debounced (emitted at most once every 3 seconds) and triggered by scroll activity and tab visibility changes.

The Page engagement event includes these props:

Prop Description
scrollDepth Furthest scroll position reached on the page, as a percentage (0–100)
timeOnPage Seconds the user has actively spent on the page (tab visible + focused)

Use EngagementTimeTracker directly

For custom integrations, you can use the lower-level EngagementTimeTracker class independently:

import { EngagementTimeTracker } from 'plausible-client';

const tracker = new EngagementTimeTracker();

tracker.start();

// Check whether the document is currently active
console.log(tracker.isActive()); // true / false

// Get total active milliseconds accumulated so far
console.log(tracker.getTotalTime());

// Subscribe to visibility changes
const unsubscribe = tracker.onVisibilityChanged((isActive) => {
  console.log('Active:', isActive);
});

// Tear down when done
unsubscribe();
tracker.stop();

Filter events

You may filter out specific events.

It may be useful to skip events of users who should not be tracked, ignore irrelevant events by its props, and for development purposes.

Just define predicate filter in config:

  • it receive event object as first parameter and event name as second
  • it must return true to send request and false to skip
import { Plausible } from 'plausible-client';

const plausible = new Plausible({
  apiHost: 'https://plausible.io',
  domain: 'example.org',
  filter(event, eventName) {
    // Skip all events while development
    if (location.hostname === 'localhost') {
      console.warn('Analytics event is skipped, since run on localhost', event);
      return false;
    }

    // Skip all events for users who don't want to be tracked
    if (window.localStorage.plausible_ignore === 'true') return false;

    // Skip events by event props
    if (event.props.group === 'no-track') return false;

    // Skip events by its name, for users who does not participate in preview program
    if (!event.props.previewEnabled && eventName.startsWith('preview:')) return false;

    // Pass all events otherwise
    return true;
  }
});

Default filters

You may use default filters

import { Plausible, filters, skipByFlag, skipForHosts } from 'plausible-client';

const plausible = new Plausible({
  apiHost: 'https://plausible.io',
  domain: 'example.org',
  // Compose many filters via `filters` call
  filter: filters(
    // Ignore events if flag is set as 'true' in provided storage
    skipByFlag('plausible_ignore', localStorage),
    // Ignore events sent from listed hostnames
    skipForHosts(['localhost'])
  )
});

Transform events

You may transform events.

It may be useful to enrich events data or redact some collected data.

Just define option transform in config

  • it receive event object and event name
  • it must return new event object.
import { Plausible } from 'plausible-client';

const plausible = new Plausible({
  apiHost: 'https://plausible.io',
  domain: 'example.org',
  transform(event, eventName) {
    event.props = {
      ...event.props,
      group: 'clients',
      userId: event.props.uid ? "uuid:" + event.props.uid : undefined,
      isPreferDarkTheme: window.matchMedia("(prefers-color-scheme: dark)").matches,
    };

    return event;
  }
});

Transformation hook runs after filter.

Inject user ID

You may automatically inject user id to all events via default transformer:

import { Plausible, userId } from 'plausible-client';

const plausible = new Plausible({
  apiHost: 'https://plausible.io',
  domain: 'example.org',
  transform: userId(),
});

If no config provided to a transformer, user ID will be persist in localStorage with key plausible_uid.

You may customize how user ID is stored via storage option. You may pass any implementation of Storage like localStorage or sessionStorage.

Also you may pass CookieStorage that implements Storage interface.

import { Plausible, userId, BrowserUIDStorage, CookieStorage } from 'plausible-client';

const plausible = new Plausible({
  apiHost: 'https://plausible.io',
  domain: 'example.org',

  // User ID will be persist in localStorage with key `plausible_uid`
  transform: userId({ 
    storage: new BrowserUIDStorage({
      // Store UID in JS cookies
      store: CookieStorage(),
      // Customize storage key to persist ID
      key: 'uid'
    }),
  }),
});

Development

plausible-client is an truth open source project, so you are welcome on project github repository to contribute a code, make issues with feature requests and bug reports.

You may contribute to a project if you tell about plausible-client to your friends.