@indiebob/tracker
v0.2.0
Published
Privacy-first analytics SDK for web applications. <5KB gzipped. No cookies.
Downloads
329
Maintainers
Readme
@indiebob/tracker
Privacy-first analytics SDK for web applications. Under 5KB gzipped. No cookies. GDPR/CCPA/GPC compliant.
Installation
npm install @indiebob/tracker
# or
pnpm add @indiebob/trackerOr via script tag (CDN):
<script src="https://assets.indiebob.com/tracker/latest/indiebob.min.js"></script>Quick Start
Three lines to start tracking:
import { IndieBob } from '@indiebob/tracker'
const bob = IndieBob.init({ projectId: 'proj_your16charId' })
bob.page()That's it. Page views are now tracked. UTM parameters are auto-captured.
Configuration
const bob = IndieBob.init({
projectId: 'proj_your16charId', // Required. From your IndieBob dashboard.
endpoint: 'https://indiebob.com/api/collect', // Optional. Default shown.
debug: false, // Optional. Logs events to console when true.
respectDnt: true, // Optional. Honor browser Do Not Track. Default: true.
respectGpc: true, // Optional. Honor Global Privacy Control. Default: true.
autoDetect: true, // Optional. Auto-detect signups, logins, checkouts, etc. Default: true.
})| Option | Type | Default | Description |
|---|---|---|---|
| projectId | string | required | Your SDK project ID from the dashboard |
| endpoint | string | 'https://indiebob.com/api/collect' | Override for self-hosted instances |
| debug | boolean | false | Log all events to browser console |
| respectDnt | boolean | true | Honor the browser's Do Not Track header |
| respectGpc | boolean | true | Honor Global Privacy Control signal (legally binding in CA/GDPR) |
| autoDetect | boolean \| object | true | Auto-detect user intent from URL patterns, form submissions, outbound clicks, and data attributes. See Auto-Detection. |
API Reference
IndieBob.init(config)
Creates and returns a tracker instance. Call once at app startup.
const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })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 titleAuto-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 as the userId directly.
traits — plain object, optional. Can include email for 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 / Testing
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 immediatelyAuto-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:
| Event | Triggered 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)
This 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 pagelogin_submit— form submitted on a login pagecheckout_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 (override defaults)
signupPaths: [/\/my-signup-page/i],
checkoutPaths: [/\/buy|\/purchase/i],
// Disable specific features
outboundClicks: false,
formSubmit: false,
dataAttributes: false,
},
})Set autoDetect: false to disable all auto-detection entirely.
| Option | Type | Default | Description |
|---|---|---|---|
| signupPaths | RegExp[] | built-in i18n | URL patterns for signup pages |
| loginPaths | RegExp[] | built-in i18n | URL patterns for login pages |
| pricingPaths | RegExp[] | built-in i18n | URL patterns for pricing pages |
| checkoutPaths | RegExp[] | built-in i18n | URL patterns for checkout/cart pages |
| successPaths | RegExp[] | built-in i18n | URL patterns for success/thank-you pages |
| onboardingPaths | RegExp[] | built-in i18n | URL patterns for onboarding pages |
| contactPaths | RegExp[] | built-in i18n | URL patterns for contact pages |
| dataAttributes | boolean | true | Track clicks on data-ib-event elements |
| formSubmit | boolean | true | Track form submissions on intent pages |
| outboundClicks | boolean | true | Track clicks on external links |
Framework Integrations
Vue / Nuxt
// main.ts or plugins/tracker.client.ts
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
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
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. This persists across sessions so you can track a visitor's journey before they sign up.
- Stored as
_ib_anonin localStorage - Falls back to sessionStorage if localStorage is blocked
- Never uses cookies — no consent banner required
- Cleared when
optOut()is called
Session ID
A session ID is generated per browser tab/window using crypto.randomUUID(). Sessions expire after 30 minutes of inactivity or when the tab closes.
UTM Attribution
UTM parameters (utm_source, utm_medium, utm_campaign, utm_term, utm_content) are captured from the URL automatically on init().
- First-touch attribution stored in
_ib_first_utm— never overwritten - Last-touch attribution in
_ib_last_utm— updated each time UTMs are present in the URL - Enables "which campaign drove this paying customer?" — the core attribution question
Event Batching
Events are batched for efficiency:
- 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 by Default
The SDK respects:
- Do Not Track (
navigator.doNotTrack === '1'): no events sent,respectDnt: trueby default - Global Privacy Control (
navigator.globalPrivacyControl): legally binding under CCPA in California,respectGpc: trueby default - Neither cookies nor fingerprinting is used at any point
- IP addresses are never stored by the server — only the country code derived at ingestion time
What Is and Isn't Collected
Automatically collected (on page())
| Data | What | Stored As |
|---|---|---|
| Page path | /pricing, /blog/post-1 | String |
| Page title | document.title | String |
| Referrer | document.referrer | String |
| UTM params | From URL query string | Object |
| Screen size | window.innerWidth/Height | Numbers |
| Device type | Derived from screen size | 'desktop' | 'mobile' | 'tablet' |
| Browser locale | navigator.language | String |
| Timezone | Intl.DateTimeFormat().resolvedOptions().timeZone | String |
| Country | Derived server-side from IP | String (e.g. 'US') |
Auto-detected events (when autoDetect is enabled)
| Event | Trigger |
|---|---|
| signup_start | User visits a signup/register page |
| signup_submit | User submits a form on a signup page |
| signup | First identify() call (new user) |
| login_view | User visits a login page |
| login_submit | User submits a form on a login page |
| login | Subsequent identify() call (returning user) |
| pricing_view | User visits a pricing page |
| checkout_view | User visits a checkout/cart page |
| checkout_submit | User submits a form on a checkout page |
| success_view | User visits a success/thank-you page |
| onboarding_view | User visits an onboarding page |
| contact_view | User visits a contact page |
| outbound_click | User clicks an external link |
| Custom via data-ib-event | User clicks an element with the attribute |
NOT collected
- IP addresses (never stored, used only for country lookup then discarded)
- Browser fingerprint
- Cookies
- Personal data (name, email, etc.) unless you pass it to
identify() - Cross-site tracking data
Privacy & Compliance
GDPR (EU)
The SDK does not use cookies and does not collect personal data by default. The anonymousId is a random identifier with no link to personal identity. Under GDPR, anonymous analytics may not require a consent banner — but consult your legal team.
When a user calls optOut(), all stored keys are cleared: _ib_anon, _ib_session, _ib_first_utm, _ib_last_utm, _ib_referrer, _ib_queue.
CCPA (California)
Global Privacy Control (GPC) is automatically honored. If a user has GPC enabled in their browser, the SDK sends no events. This is the legally correct behavior under CCPA as amended by CPRA.
Cookie Law (EU ePrivacy Directive)
No cookies are set. The localStorage identifiers are not cookies and fall outside the scope of the Cookie Law.
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',
})Versioning & Backwards Compatibility
This SDK follows Semantic Versioning.
1.x guarantee: All public methods listed in this README are stable. We will never:
- Remove a method in a 1.x release
- Change the behavior of an existing method without a deprecation period
- Break the wire format for events that include
v: 1
Upgrade path: npm update @indiebob/tracker — no code changes required within 1.x.
Deprecation process: Methods marked @deprecated in JSDoc will log a warning in debug: true mode. They remain functional until the next major version.
Bundle Size
| Bundle | Size (gzip) |
|---|---|
| Browser IIFE (tracker.js) | < 5KB |
| ESM tree-shakeable | < 4KB |
| Server (Node.js) | No limit |
Zero runtime dependencies.
License
MIT
