JavaScript / TypeScript SDK
@kitbase/analytics is the core TypeScript SDK for Kitbase analytics. It is fully typed, works in both browser and Node.js environments, and provides a rich API for event tracking, user identity, session management, and offline queuing.
Two Builds Available
| Build | Import | Size | Notes |
|---|---|---|---|
| Full | @kitbase/analytics | ~50KB | Includes offline queue (IndexedDB via Dexie) |
| Lite | @kitbase/analytics/lite | ~12KB | Excludes offline queue |
Use the lite build when you do not need offline support and want a smaller bundle.
Installation
npm install @kitbase/analyticsyarn add @kitbase/analyticspnpm add @kitbase/analyticsInitialization
import { init } from '@kitbase/analytics';
const kitbase = init({
sdkKey: 'YOUR_SDK_KEY',
debug: false,
baseUrl: 'https://api.kitbase.dev',
storage: localStorage,
storageKey: 'kitbase_anonymous_id',
analytics: {
autoTrackPageViews: true,
trackBfcacheRestore: true,
autoTrackOutboundLinks: true,
autoTrackClicks: true,
autoTrackScrollDepth: true,
autoTrackVisibility: true,
autoTrackWebVitals: false,
autoDetectFrustration: true,
},
offline: {
enabled: false,
maxQueueSize: 1000,
flushInterval: 30000,
flushBatchSize: 50,
maxRetries: 3,
retryBaseDelay: 1000,
},
});Configuration Options
Root Options
| Option | Type | Default | Description |
|---|---|---|---|
sdkKey | string | required | Your SDK key from the Kitbase dashboard |
debug | boolean | false | Enable debug logging to the console |
baseUrl | string | 'https://api.kitbase.dev' | API endpoint (override for self-hosted) |
storage | Storage | null | localStorage | Storage backend for anonymous ID persistence |
storageKey | string | 'kitbase_anonymous_id' | Key used in storage for the anonymous ID |
Analytics Options
| Option | Type | Default | Description |
|---|---|---|---|
autoTrackPageViews | boolean | true | Automatically track page views on navigation |
trackBfcacheRestore | boolean | true | Track a pageview when the browser restores a page from back-forward cache (bfcache). Important for MPA sites where back/forward navigation does not re-execute scripts. |
autoTrackOutboundLinks | boolean | true | Automatically track clicks on external links |
autoTrackClicks | boolean | true | Automatically track clicks on interactive elements |
autoTrackScrollDepth | boolean | true | Automatically track maximum scroll depth per page |
autoTrackVisibility | boolean | true | Track element visibility via data-kb-track-visibility attributes |
autoTrackWebVitals | boolean | false | Track Core Web Vitals (LCP, CLS, INP, FCP, TTFB) |
autoDetectFrustration | boolean | true | Detect frustration signals (rage clicks and dead clicks) |
Offline Options
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable offline queue |
maxQueueSize | number | 1000 | Maximum number of events to queue |
flushInterval | number | 30000 | Auto-flush interval in milliseconds |
flushBatchSize | number | 50 | Number of events per flush batch |
maxRetries | number | 3 | Maximum retry attempts for failed events |
retryBaseDelay | number | 1000 | Base delay in milliseconds for exponential backoff |
API Reference
Event Tracking
track(options: TrackOptions): Promise<TrackResponse>
Track a custom event.
const response = await kitbase.track({
channel: 'payments',
event: 'New Subscription',
user_id: 'user-123',
icon: '💰',
notify: true,
description: 'User subscribed to premium plan',
tags: {
plan: 'premium',
cycle: 'monthly',
amount: 9.99,
},
});
console.log(response.id); // "evt_abc123"
console.log(response.timestamp); // "2026-02-13T10:30:00.000Z"trackPageView(options?: PageViewOptions): Promise<TrackResponse>
Track a page view manually. When autoTrackPageViews is enabled, page views are tracked automatically on route changes; use this method for cases where you need explicit control.
await kitbase.trackPageView({
path: '/products/123',
referrer: 'https://google.com',
title: 'Product Details',
});
// Or with defaults (uses current window.location and document.title)
await kitbase.trackPageView();trackRevenue(options: RevenueOptions): Promise<TrackResponse>
Track a revenue event.
await kitbase.trackRevenue({
amount: 99.99,
currency: 'USD',
orderId: 'order-123',
productId: 'prod-456',
tags: { plan: 'premium' },
});trackOutboundLink(options: OutboundLinkOptions): Promise<TrackResponse>
Track an outbound link click manually. When autoTrackOutboundLinks is enabled, external link clicks are tracked automatically; use this method when you need to track programmatic navigations.
await kitbase.trackOutboundLink({
url: 'https://partner-site.com',
text: 'Visit Partner',
});User Identity
identify(options: IdentifyOptions): Promise<void>
Identify a user and link their anonymous activity to a known user ID. Call this immediately after login or signup. The SDK sends the current anonymous ID along with the user ID so the backend can merge the two profiles.
await kitbase.identify({
userId: 'user-123',
traits: {
email: 'user@example.com',
name: 'Jane Doe',
plan: 'premium',
},
});WARNING
You must explicitly call identify() after authentication. Kitbase does not automatically link anonymous users to identified users.
getAnonymousId(): string
Get the current anonymous user ID. This is a UUID v4 that is auto-generated and persisted in storage.
const anonymousId = kitbase.getAnonymousId();
// => "550e8400-e29b-41d4-a716-446655440000"getUserId(): string | null
Get the currently identified user ID, or null if no user has been identified.
const userId = kitbase.getUserId();
// => "user-123" or nullreset(): void
Reset the user identity. Clears the identified user ID, generates a new anonymous ID, and starts a new session.
kitbase.reset();Session Management
getSession(): SessionInfo
Get the current session information.
const session = kitbase.getSession();
// => {
// id: 'sess_abc123',
// startedAt: 1705321800000,
// lastActivityAt: 1705323600000
// }getSessionId(): string
Get the current session ID.
const sessionId = kitbase.getSessionId();
// => "sess_abc123"Super Properties
Super properties are key-value pairs that are automatically merged into the tags of every event. They are useful for persistent context such as app version, environment, or user segment.
In-Memory Storage
Super properties are stored in memory and reset when the page reloads. For persistent properties, store them externally and call register() on initialization.
register(properties: Record<string, unknown>): void
Set super properties. If a property already exists, it is overwritten.
kitbase.register({
app_version: '2.1.0',
environment: 'production',
platform: 'web',
});registerOnce(properties: Record<string, unknown>): void
Set super properties only if they are not already set. Useful for values like first_visit_date that should never be overwritten.
kitbase.registerOnce({
first_seen: new Date().toISOString(),
});unregister(key: string): void
Remove a single super property.
kitbase.unregister('platform');getSuperProperties(): Record<string, unknown>
Get all currently registered super properties.
const props = kitbase.getSuperProperties();
// => { app_version: '2.1.0', environment: 'production' }clearSuperProperties(): void
Remove all super properties.
kitbase.clearSuperProperties();Timed Events
Start a timer before an action and automatically include the elapsed time as a $duration tag (in milliseconds) when the event is tracked.
timeEvent(eventName: string): void
Start timing an event. When track() is later called with the same event name, the $duration tag is automatically appended.
kitbase.timeEvent('Checkout Completed');
// ... user completes checkout ...
await kitbase.track({
channel: 'purchases',
event: 'Checkout Completed',
tags: { order_id: 'order-123' },
});
// Result: tags include { order_id: 'order-123', $duration: 45000 }cancelTimeEvent(eventName: string): void
Cancel a running timer without tracking the event.
kitbase.cancelTimeEvent('Checkout Completed');getTimedEvents(): string[]
Get a list of all events that currently have active timers.
const activeTimers = kitbase.getTimedEvents();
// => ['Checkout Completed', 'Video Watched']getEventDuration(eventName: string): number
Get the elapsed time in milliseconds for a timed event without stopping the timer.
const elapsed = kitbase.getEventDuration('Video Watched');
// => 45000Offline Queue
When offline mode is enabled, events are queued locally and automatically flushed when the connection is restored. See Offline Support for full details.
getQueueStats(): Promise<QueueStats>
Get statistics about the offline queue.
const stats = await kitbase.getQueueStats();
// => { pending: 15, failed: 2, total: 17 }flushQueue(): Promise<void>
Force send all queued events immediately.
await kitbase.flushQueue();clearQueue(): Promise<void>
Clear all queued events without sending them.
await kitbase.clearQueue();Debug & Lifecycle
setDebugMode(enabled: boolean): void
Toggle debug logging at runtime.
kitbase.setDebugMode(true);isDebugMode(): boolean
Check whether debug mode is currently enabled.
if (kitbase.isDebugMode()) {
console.log('Debug mode is active');
}enableAutoPageViews(): void
Enable automatic page view tracking after initialization.
kitbase.enableAutoPageViews();shutdown(): Promise<void>
Flush all pending events, clean up timers and listeners, and release resources. Call this when your application unmounts.
await kitbase.shutdown();TypeScript Types
TrackOptions
interface TrackOptions {
/** Category for the event (e.g., "payments", "users") */
channel: string;
/** Name of the event (e.g., "New Subscription") */
event: string;
/** Identifier for the user */
user_id?: string;
/** Emoji or icon name */
icon?: string;
/** Send a real-time notification */
notify?: boolean;
/** Additional context about the event */
description?: string;
/** Key-value metadata */
tags?: Record<string, unknown>;
}TrackResponse
interface TrackResponse {
/** Unique event ID (e.g., "evt_abc123") */
id: string;
/** Event name */
event: string;
/** ISO 8601 timestamp */
timestamp: string;
}PageViewOptions
interface PageViewOptions {
/** Page path (defaults to window.location.pathname) */
path?: string;
/** Referrer URL (defaults to document.referrer) */
referrer?: string;
/** Page title (defaults to document.title) */
title?: string;
}RevenueOptions
interface RevenueOptions {
/** Revenue amount */
amount: number;
/** Currency code (e.g., "USD") */
currency?: string;
/** Order identifier */
orderId?: string;
/** Product identifier */
productId?: string;
/** Additional metadata */
tags?: object;
}OutboundLinkOptions
interface OutboundLinkOptions {
/** Full URL of the outbound link */
url: string;
/** Link text content */
text?: string;
}IdentifyOptions
interface IdentifyOptions {
/** Unique user identifier */
userId: string;
/** User properties (email, name, plan, etc.) */
traits?: Record<string, unknown>;
}SessionInfo
interface SessionInfo {
/** Session ID (e.g., "sess_abc123") */
id: string;
/** Timestamp when the session started */
startedAt: number;
/** Timestamp of the last recorded activity */
lastActivityAt: number;
}QueueStats
interface QueueStats {
/** Events waiting to be sent */
pending: number;
/** Events that failed and will be retried */
failed: number;
/** Total events in queue */
total: number;
}Error Types
All error classes extend KitbaseError, which itself extends the native Error class. Import them directly from the package:
import {
KitbaseError,
AuthenticationError,
ValidationError,
TimeoutError,
ApiError,
} from '@kitbase/analytics';KitbaseError
Base class for all SDK errors.
class KitbaseError extends Error {
message: string;
}AuthenticationError
Thrown when the API key is invalid or missing.
class AuthenticationError extends KitbaseError {
message: string; // "Invalid or missing API key"
}Common causes: incorrect sdkKey value, expired key, or key from a different environment.
ValidationError
Thrown when a required field is missing or a field value is invalid.
class ValidationError extends KitbaseError {
message: string; // Error description
field: string; // The field that failed validation (e.g., "channel")
}TimeoutError
Thrown when a request exceeds the 30-second timeout.
class TimeoutError extends KitbaseError {
message: string; // "Request timed out"
}ApiError
Thrown when the API returns an error response (4xx or 5xx).
class ApiError extends KitbaseError {
message: string; // Error description from API
statusCode: number; // HTTP status code (e.g., 429, 500)
response: unknown; // Raw response body
}Error Handling Example
import {
KitbaseError,
AuthenticationError,
ValidationError,
TimeoutError,
ApiError,
} from '@kitbase/analytics';
try {
await kitbase.track({
channel: 'payments',
event: 'New Subscription',
});
} catch (error) {
if (error instanceof AuthenticationError) {
// Invalid API key - check your sdkKey
console.error('Authentication failed');
} else if (error instanceof ValidationError) {
// Missing required field
console.error(`Validation error on field: ${error.field}`);
} else if (error instanceof TimeoutError) {
// Request timed out - safe to retry
console.error('Request timed out');
} else if (error instanceof ApiError) {
// API returned an error
console.error(`API error ${error.statusCode}:`, error.response);
} else if (error instanceof KitbaseError) {
// Generic SDK error
console.error(error.message);
}
}Retry with Exponential Backoff
import type { TrackOptions, TrackResponse, KitbaseAnalytics } from '@kitbase/analytics';
import {
AuthenticationError,
ValidationError,
TimeoutError,
ApiError,
} from '@kitbase/analytics';
async function trackWithRetry(
kitbase: KitbaseAnalytics,
options: TrackOptions,
retries = 3
): Promise<TrackResponse> {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await kitbase.track(options);
} catch (error) {
// Never retry auth or validation errors
if (error instanceof AuthenticationError) throw error;
if (error instanceof ValidationError) throw error;
const shouldRetry =
error instanceof TimeoutError ||
(error instanceof ApiError && error.statusCode >= 500);
if (shouldRetry && attempt < retries) {
const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}Framework Examples
React
import { init } from '@kitbase/analytics';
import { useEffect } from 'react';
// Initialize once outside of components
const kitbase = init({ sdkKey: 'YOUR_SDK_KEY' });
function App() {
const { user } = useAuth();
// Identify the user after authentication
useEffect(() => {
if (user) {
kitbase.identify({
userId: user.id,
traits: {
email: user.email,
name: user.name,
plan: user.plan,
},
});
}
}, [user]);
// Cleanup on unmount
useEffect(() => {
return () => {
kitbase.shutdown();
};
}, []);
return <YourApp />;
}
// Track custom events anywhere in your app
function ExportButton() {
const handleExport = async () => {
kitbase.timeEvent('Export Generated');
await generateExport();
await kitbase.track({
channel: 'engagement',
event: 'Export Generated',
tags: { format: 'pdf' },
});
};
return <button onClick={handleExport}>Export PDF</button>;
}Next.js
'use client';
import { init } from '@kitbase/analytics';
import { useEffect } from 'react';
// Initialize on the client side only
const kitbase =
typeof window !== 'undefined'
? init({ sdkKey: 'YOUR_SDK_KEY' })
: null;
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
const { user } = useAuth();
useEffect(() => {
if (user && kitbase) {
kitbase.identify({
userId: user.id,
traits: { email: user.email },
});
}
}, [user]);
useEffect(() => {
return () => {
kitbase?.shutdown();
};
}, []);
return <>{children}</>;
}
// Export for use across your app
export { kitbase };// app/layout.tsx
import { AnalyticsProvider } from './analytics-provider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AnalyticsProvider>{children}</AnalyticsProvider>
</body>
</html>
);
}Vue
// plugins/kitbase.ts
import { init } from '@kitbase/analytics';
const kitbase = init({ sdkKey: 'YOUR_SDK_KEY' });
export default {
install(app) {
app.provide('kitbase', kitbase);
app.config.globalProperties.$kitbase = kitbase;
},
};
export { kitbase };// main.ts
import { createApp } from 'vue';
import kitbasePlugin from './plugins/kitbase';
import App from './App.vue';
const app = createApp(App);
app.use(kitbasePlugin);
app.mount('#app');<!-- components/ExportButton.vue -->
<script setup lang="ts">
import { inject } from 'vue';
import type { KitbaseAnalytics } from '@kitbase/analytics';
const kitbase = inject<KitbaseAnalytics>('kitbase')!;
async function handleExport() {
await kitbase.track({
channel: 'engagement',
event: 'Export Generated',
tags: { format: 'csv' },
});
}
</script>
<template>
<button @click="handleExport">Export CSV</button>
</template>Nuxt
// plugins/kitbase.client.ts
import { init } from '@kitbase/analytics';
export default defineNuxtPlugin(() => {
const kitbase = init({ sdkKey: 'YOUR_SDK_KEY' });
return {
provide: {
kitbase,
},
};
});<!-- pages/index.vue -->
<script setup lang="ts">
const { $kitbase } = useNuxtApp();
async function onSignUp() {
await $kitbase.identify({ userId: 'user-123' });
await $kitbase.track({
channel: 'users',
event: 'User Signed Up',
icon: '👤',
notify: true,
});
}
</script>Offline Support
How It Works
When offline mode is enabled, the SDK queues events locally when the network is unavailable and automatically flushes them when the connection is restored.
User action
|
track() called
|
+------------------+
| Online? |
+------------------+
| |
Yes No
| |
Send to Queue in
API IndexedDB
| |
| +----------+
| | Network | <-- 'online' event
| | restored |
| +----------+
| |
| Flush queue
| |
Done DoneEnabling Offline Mode
const kitbase = init({
sdkKey: 'YOUR_SDK_KEY',
offline: {
enabled: true,
maxQueueSize: 1000,
flushInterval: 30000,
flushBatchSize: 50,
maxRetries: 3,
retryBaseDelay: 1000,
},
});Full Build Required
Offline mode requires the full build (@kitbase/analytics). The lite build (@kitbase/analytics/lite) does not include offline queue support.
Retry Logic
Failed events are retried with exponential backoff. The delay is calculated as retryBaseDelay * 2^attempt:
| Attempt | Delay | Action |
|---|---|---|
| 1st retry | 1 second | Retry |
| 2nd retry | 2 seconds | Retry |
| 3rd retry | 4 seconds | Retry |
| After max retries | -- | Event dropped |
Storage
| Environment | Backend | Persistence |
|---|---|---|
| Browser | IndexedDB via Dexie | Events survive page reloads and browser restarts |
| Node.js | In-memory | Events are lost on process restart |
Queue Management
// Check queue health
const stats = await kitbase.getQueueStats();
if (stats.pending > 0) {
showOfflineIndicator();
}
// Force flush (e.g., before navigation)
await kitbase.flushQueue();
// Clear queue (e.g., discard pending events)
await kitbase.clearQueue();Progressive Web App Integration
// Flush on service worker sync
self.addEventListener('sync', async (event) => {
if (event.tag === 'kitbase-sync') {
event.waitUntil(kitbase.flushQueue());
}
});Queue Overflow
When the queue reaches maxQueueSize, new events are dropped. Monitor getQueueStats() for critical applications.
Bot Filtering
Kitbase automatically filters out bot and crawler traffic. Events are not sent when your app is visited by headless browsers (Puppeteer, Playwright), automated testing tools (Selenium, WebDriver), HTTP clients (curl, wget), search engine crawlers (Googlebot, Bingbot), or social media crawlers (Facebook, Twitter, Slack). No configuration is required.
Auto-Tracked Events
When auto-tracking is enabled (the default), the SDK fires the following events on the internal __analytics channel:
| Event | Config Flag | Trigger |
|---|---|---|
screen_view | autoTrackPageViews | Page load, pushState, popstate |
screen_view | trackBfcacheRestore | Page restored from bfcache (back/forward in MPAs) |
outbound_link | autoTrackOutboundLinks | Click on external link |
click | autoTrackClicks | Click on interactive element |
scroll_depth | autoTrackScrollDepth | Page unload (reports max scroll %) |
rage_click | autoDetectFrustration | 3+ clicks within 1s in same area |
dead_click | autoDetectFrustration | Click with no DOM change within 1s |
web_vitals | autoTrackWebVitals | Once per page load (opt-in) |
revenue | -- | Explicit trackRevenue() call |
Data Attributes (No-Code Tracking)
Track events from HTML without writing JavaScript by annotating elements with data attributes:
<!-- Track a named click event -->
<button data-kb-track-click="CTA Clicked">Sign Up Now</button>
<!-- Track with a custom channel -->
<button
data-kb-track-click="Add to Cart"
data-kb-click-channel="ecommerce"
>
Add to Cart
</button>
<!-- Track element visibility duration -->
<section data-kb-track-visibility="Pricing Section Viewed">
...
</section>
<!-- Visibility with custom threshold and channel -->
<div
data-kb-track-visibility="Hero Banner Viewed"
data-kb-visibility-channel="marketing"
data-kb-visibility-threshold="0.75"
>
...
</div>For the complete auto-tracked events reference, see Autocapture.
Best Practices
Initialize Once, Reuse Everywhere
Create a single instance with init() and export it for use across your application. Do not create multiple instances.
// lib/kitbase.ts
import { init } from '@kitbase/analytics';
export const kitbase = init({ sdkKey: 'YOUR_SDK_KEY' });// Anywhere in your app
import { kitbase } from '@/lib/kitbase';
await kitbase.track({ channel: 'engagement', event: 'Feature Used' });Use Consistent Channel Naming
Use lowercase, plural nouns for channel names:
| Channel | Use Case |
|---|---|
payments | Subscriptions, purchases, refunds |
users | Signups, logins, profile updates |
errors | Application errors, exceptions |
features | Feature usage tracking |
engagement | General user interactions |
system | Deployments, server events |
Leverage Auto-Tracking
Prefer auto-tracking over manual tracking wherever possible. The SDK handles page views, sessions, outbound links, clicks, and scroll depth automatically. Reserve manual track() calls for business-specific events like purchases, signups, and feature usage.
Handle Errors Gracefully
Never let tracking failures break your application. Wrap calls in a safe helper:
async function safeTrack(options: TrackOptions): Promise<void> {
try {
await kitbase.track(options);
} catch (error) {
// Log but don't throw - tracking is non-critical
console.error('Event tracking failed:', error);
}
}Call shutdown() on Unmount
Always call shutdown() when your application unmounts to ensure all queued events are sent and resources are released:
// React
useEffect(() => {
return () => {
kitbase.shutdown();
};
}, []);
// Vanilla JS
window.addEventListener('beforeunload', () => {
kitbase.shutdown();
});Use Super Properties for Shared Context
Set properties like app version and environment once via register() instead of passing them on every track() call:
kitbase.register({
app_version: '2.1.0',
environment: process.env.NODE_ENV,
});
// All subsequent events automatically include these tags
await kitbase.track({ channel: 'users', event: 'Signup' });
// tags: { app_version: '2.1.0', environment: 'production', ... }Use snake_case for Tag Keys
// Good
tags: { user_type: 'premium', error_code: 'E001' }
// Bad
tags: { UserType: 'premium', 'error-code': 'E001' }Next Steps
- Analytics Dashboard -- Dashboard metrics, breakdowns, and filtering
- Autocapture -- Complete reference for every automatic event
- Custom Events -- Channels, tags, notifications, and use cases
- Identify Users -- Anonymous tracking and identity resolution
- Error Handling -- Full error handling reference for all SDKs
- Analytics Guide -- Privacy, bot filtering, and data enrichment