Engineering

Inside the Pug Web SDK

A web analytics SDK should do more than fire events. Here’s what Pug’s browser SDK handles for you — autocapture, identity, consent, reliable delivery, and typed events — and how each works.

The Pug Web SDK is a small TypeScript library for the browser. You call init() once and it starts capturing behavior, resolving identity, managing sessions, and delivering events reliably — the plumbing every product-analytics integration needs but few SDKs handle well. It’s open source, and because Pug is self-hostable, the events it sends can stay on your own infrastructure.

Autocapture, out of the box

After init(), the SDK autocaptures six kinds of interaction with no extra code: page views (including SPA route changes via patched history), clicks (with element, text, and coordinates), scroll depth, form starts and submits, and two frustration signals — rage clicks (rapid repeated clicks) and dead clicks (clicks that change nothing). You can narrow capture to an allowlist when you only want some of them:

init(projectId, {
  apiKey,
  autoCapture: { pageView: true, click: true, scroll: false },
})

A typed track(), and well-known events

Beyond autocapture, track() sends any custom event. Pug also ships a set of well-known events — standard names like purchase or signup that come with typed properties and runtime validation, so bad data is caught at the source rather than discovered in a dashboard weeks later:

track('signup', { plan: 'pro' })

track('purchase', { productId: 'sku_123', amount: 49, currency: 'USD' })

Extra properties beyond the schema are allowed and sent as custom properties — standard where it helps, flexible where you need it.

Identity that merges

Before sign-in, events accrue to a persistent anonymous ID. When a visitor identifies, the SDK merges that anonymous history into a unified profile and attaches traits:

identify('user_123', { email: '[email protected]', plan: 'pro' })

The first identify() includes the anonymous ID so the server merges the two profiles; later calls just update traits. reset() clears identity on logout so the next visitor starts clean.

Sessions, automatically

Sessions are handled for you — created lazily, persisted, and synced across tabs, with a configurable idle timeout (30 minutes by default) and a maximum length. There are no timers to leak; expiry is evaluated when events fire. You can force a new session on events like logout if you need to.

Built for consent

The SDK is consent-first, which matters for GDPR. Initialize with consent denied and no automatic listeners attach, while manual track() and identify() are dropped — not secretly queued for later replay. Grant consent when the user agrees:

init(projectId, { apiKey, defaultTrackingConsent: 'denied' })

// after the user agrees:
optInTracking()

Opt-out can be made sticky across reloads, and revoking consent tears the listeners back down automatically.

Reliable delivery

Events are buffered and flushed when a batch fills or a timer expires, with a localStorage-backed queue that survives reloads. On pagehide and visibilitychange, the SDK switches to the browser’s sendBeacon so the final batch lands even as the page unloads. Transient network errors roll back and retry; a priority event can bypass batching with an immediate flag.

Properties you don’t have to set

Every event is enriched automatically with context: URL, referrer, locale, screen size, page title, SDK version, UTM parameters, and device and browser details from UA Client Hints where available — then geo is added server-side on ingest. You get rich, queryable events without passing any of it by hand.

Optional web push

Push is opt-in and tree-shakeable — import the push helpers only if you need them, and analytics-only users pay zero bundle cost. The SDK can register a service worker, subscribe the browser with your VAPID key, link the push device to a profile on identify(), and track notification clicks reliably. These primitives ship today; the dashboard Campaigns composer that orchestrates messages on top of them is coming soon.

Own the whole pipeline

Because Pug is open source and self-hostable, the SDK talks to a backend you can run yourself — one Go binary, your data on your servers. See the SDKs page for install and the platform overview for how events flow from the SDK to insights.

FAQ

Common questions

What does the Pug Web SDK track automatically?

After a single init(), it autocaptures page views, clicks, scrolls, form starts and submits, plus rage clicks and dead clicks — no manual instrumentation. You can narrow this to an allowlist, or disable autocapture entirely and send only the events you choose.

Is the Pug Web SDK GDPR-friendly?

It’s built consent-first. Start with tracking consent denied and no automatic listeners attach and manual calls are dropped (not queued); call optInTracking() once the user agrees. Opt-out can be made sticky across reloads. Self-hosting keeps the events on your own servers.

How does the SDK handle identity?

Before sign-in, events accrue to a persistent anonymous ID. Calling identify() merges that anonymous history into the person’s profile and attaches traits like plan or email, so pre- and post-signup behavior live on one timeline. reset() clears identity on logout.

Does it lose events on page navigation?

No. Events are batched and flushed on size or a timer, and the SDK uses the browser’s sendBeacon on pagehide/visibilitychange so the final batch is delivered reliably even as the page unloads. Transient network errors are retried.

Add the Web SDK in minutes.

Open source, self-hostable on one Go binary, and free during open beta. Drop in the SDK and see live events in minutes.