@indiebob/tracker

v0.2.0

Privacy-first analytics SDK. Under 5KB gzipped. No cookies. GDPR, CCPA, and GPC compliant.

<5KB gzipNo cookiesGDPR / CCPA / GPCZero dependencies

Installation

npm install @indiebob/tracker
# or
pnpm add @indiebob/tracker

Or via script tag (no build step required):

<script src="https://assets.indiebob.com/tracker/latest/indiebob.min.js"></script>

Quick Start

Three lines to start tracking page views and UTM attribution:

import { IndieBob } from '@indiebob/tracker'

const bob = IndieBob.init({ projectId: 'proj_your16charId' })
bob.page()

Get your projectId from your IndieBob dashboard → Project Settings → SDK Key.


Configuration

const bob = IndieBob.init({
  projectId: 'proj_your16charId',              // Required
  endpoint: 'https://indiebob.com/api/collect', // Optional. Default shown.
  debug: false,                                 // Optional. Log events to console.
  respectDnt: true,                             // Optional. Honor Do Not Track.
  respectGpc: true,                             // Optional. Honor Global Privacy Control.
  autoDetect: true,                             // Optional. Auto-detect signups, logins, etc.
})
OptionTypeDefaultDescription
projectIdstringrequiredYour SDK project ID from the dashboard
endpointstring'https://indiebob.com/api/collect'Override for self-hosted instances
debugbooleanfalseLog all events to browser console
respectDntbooleantrueHonor the browser's Do Not Track header
respectGpcbooleantrueHonor Global Privacy Control (legally binding in CA/GDPR)
autoDetectboolean | objecttrueAuto-detect user intent from URL patterns, form submissions, outbound clicks, and data attributes. See Auto-Detection.

API Reference

bob.page(overrides?)

Track a page view. Call this on every page load and SPA navigation.

bob.page()                            // Auto-detects path, title, referrer
bob.page({ path: '/pricing' })        // Override path
bob.page({ title: 'Pricing Page' })  // Override title

Auto-detected: URL path, page title, referrer, UTM parameters, device type.

bob.track(event, properties?)

Track a custom event.

bob.track('signup_started')
bob.track('trial_activated', { plan: 'pro' })
bob.track('feature_used', { feature: 'csv_export', count: 3 })
bob.track('checkout_completed', { revenue: 29.00, currency: 'USD', plan: 'pro-monthly' })
  • event — string, required. Max 256 characters. Use snake_case by convention.
  • properties — plain object, optional. Values must be JSON-serializable.

bob.identify(userId, traits?)

Associate the current anonymous visitor with a known user ID. Call this after login or signup.

bob.identify('user_123', {
  email: '[email protected]',
  name: 'Jane Smith',
  plan: 'pro',
  createdAt: '2026-01-15',
})
  • userId — string, required. Your internal user ID. Never pass PII directly as the userId.
  • traits — plain object, optional. Include email to enable Stripe attribution linking.

After identify(), all subsequent events are linked to this userId until the session ends.

bob.revenue(data)

Track a revenue event. Use alongside Stripe webhooks for complete attribution.

bob.revenue({
  amount: 29.00,        // Required. Positive number.
  currency: 'USD',      // Optional. Default: 'USD'. ISO 4217 code.
  plan: 'pro-monthly',  // Optional. Your plan name.
  type: 'new',          // Optional. 'new' | 'renewal' | 'upgrade' | 'downgrade'
})

Privacy Controls

bob.optOut()       // Stop all tracking. Clears all stored identifiers.
bob.optIn()        // Re-enable tracking.
bob.isOptedOut()   // Returns boolean.

Debug helpers

bob.getAnonymousId()  // Returns the current anonymous ID
bob.getSessionId()    // Returns the current session ID
bob.getConfig()       // Returns a read-only copy of the config
bob.flush()           // Force-send all queued events immediately

Auto-Detection

The SDK automatically fires semantic events out of the box — no manual bob.track() calls needed for common user journeys. This powers IndieBob's attribution funnels (visit → signup → checkout → paid).

URL Intent Detection

When bob.page() is called, the SDK checks the URL path against known patterns (multilingual: EN, ES, FR, DE, PT, IT, NL) and fires the corresponding event:

EventTriggered by
signup_start/signup, /register, /join, /registro, /inscription, /anmeldung, ...
login_view/login, /sign-in, /iniciar-sesion, /connexion, /anmelden, ...
pricing_view/pricing, /plans, /precios, /tarifs, /preise, ...
checkout_view/checkout, /cart, /basket, /panier, /warenkorb, /kasse, ...
success_view/success, /thank-you, /confirmation, /danke, /merci, ...
onboarding_view/onboarding, /welcome, /getting-started, /bienvenido, ...
contact_view/contact, /contacto, /kontakt, /nous-contacter, ...

Signup vs Login Detection

When bob.identify() is called:

  • First call for this visitor fires signup (new user)
  • Subsequent calls fire login (returning user)

Uses a localStorage flag — no server round-trip needed.

Form Submit Detection

On signup, login, and checkout pages, form submissions are automatically tracked:

  • signup_submit — form submitted on a signup page
  • login_submit — form submitted on a login page
  • checkout_submit — form submitted on a checkout page

Outbound Click Tracking

Clicks on links pointing to external domains automatically fire outbound_click with { url, hostname } properties.

Data Attribute Tracking

Add data-ib-event to any element for zero-JS custom event tracking:

<button data-ib-event="add_to_cart" data-ib-plan="pro" data-ib-price="29">
  Add to Cart
</button>

This fires track('add_to_cart', { plan: 'pro', price: '29' }) on click. All data-ib-* attributes (except data-ib-event) become event properties.

Customizing Auto-Detection

Override the default URL patterns or disable specific features:

const bob = IndieBob.init({
  projectId: 'proj_abc123',
  autoDetect: {
    // Custom URL patterns — strings or RegExp (override defaults)
    signupPaths: ['/my-signup-page'],         // string: matched via path.includes()
    checkoutPaths: [/\/buy|\/purchase/i],     // RegExp: matched via .test()

    // Disable specific features
    outboundClicks: false,
    formSubmit: false,
    dataAttributes: false,
  },
})

Set autoDetect: false to disable all auto-detection entirely.

OptionTypeDefaultDescription
signupPaths(string | RegExp)[]built-in i18nURL patterns for signup pages
loginPaths(string | RegExp)[]built-in i18nURL patterns for login pages
pricingPaths(string | RegExp)[]built-in i18nURL patterns for pricing pages
checkoutPaths(string | RegExp)[]built-in i18nURL patterns for checkout/cart pages
successPaths(string | RegExp)[]built-in i18nURL patterns for success/thank-you pages
onboardingPaths(string | RegExp)[]built-in i18nURL patterns for onboarding pages
contactPaths(string | RegExp)[]built-in i18nURL patterns for contact pages
dataAttributesbooleantrueTrack clicks on data-ib-event elements
formSubmitbooleantrueTrack form submissions on intent pages
outboundClicksbooleantrueTrack clicks on external links

Framework Integrations

Vue / Nuxt

import { IndieBobPlugin } from '@indiebob/tracker/vue'

app.use(IndieBobPlugin, { projectId: 'proj_abc123def456ghij' })

Then in any component:

import { useTracker } from '@indiebob/tracker/vue'

const bob = useTracker()
bob.track('button_clicked', { button: 'cta_hero' })

Nuxt plugin (plugins/tracker.client.ts):

import { IndieBobPlugin } from '@indiebob/tracker/vue'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(IndieBobPlugin, {
    projectId: useRuntimeConfig().public.trackerProjectId,
  })
})

Page views auto-track on vue-router navigation.

React / Next.js

import { IndieBob } from '@indiebob/tracker'
import { useEffect } from 'react'

// Initialize once at app root
const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })

// In a page component or layout — call bob.page() on every navigation
useEffect(() => {
  bob.page()
}, [pathname])

Vanilla JS (Script Tag)

<script src="https://assets.indiebob.com/tracker/latest/indiebob.min.js"></script>
<script>
  const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })
  bob.page()
</script>

Server-Side (Node.js)

import { IndieBob } from '@indiebob/tracker/server'

const bob = IndieBob.initServer({ projectId: 'proj_abc123def456ghij' })
bob.track('server_event', { source: 'cron_job' })

How It Works

Anonymous ID

A random 21-character ID is generated on first visit and stored in localStorage as _ib_anon. This persists across sessions so you can track a visitor's journey before they sign up. Falls back to sessionStorage in private browsing. Never uses cookies.

Session ID

Generated per browser tab via crypto.randomUUID(). Sessions expire after 30 minutes of inactivity or when the tab closes.

UTM Attribution

UTM parameters are captured from the URL automatically on init().

  • _ib_first_utm — first-touch attribution, never overwritten
  • _ib_last_utm — last-touch attribution, updated each time UTMs are present

This enables the core attribution question: "which campaign drove this paying customer?"

Event Batching

  • Sent when 10 events accumulate
  • Sent every 5 seconds regardless
  • Sent immediately when the page becomes hidden (tab switch, close)
  • Queued in localStorage when offline; flushed when back online

Privacy & Compliance

GDPR (EU)

No cookies, no personal data by default. Anonymous analytics may not require a consent banner — but consult your legal team.

CCPA (California)

Global Privacy Control (GPC) is automatically honored. If a user has GPC enabled, the SDK sends no events — the legally correct behavior under CCPA/CPRA.

Cookie Law (EU)

No cookies are set. localStorage identifiers are not cookies and fall outside the scope of the EU ePrivacy Directive.


Self-Hosting

Point the SDK at your own IndieBob instance:

const bob = IndieBob.init({
  projectId: 'proj_abc123def456ghij',
  endpoint: 'https://your-indiebob.example.com/api/collect',
})

Bundle Size

BundleSize (gzip)
Browser IIFE (script tag)< 5KB
ESM (tree-shakeable)< 4KB
Server (Node.js)No limit

Zero runtime dependencies.

@indiebob/tracker v0.2.0Dashboard →