Skip to content

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

BuildImportSizeNotes
Full@kitbase/analytics~50KBIncludes offline queue (IndexedDB via Dexie)
Lite@kitbase/analytics/lite~12KBExcludes offline queue

Use the lite build when you do not need offline support and want a smaller bundle.

Installation

bash
npm install @kitbase/analytics
bash
yarn add @kitbase/analytics
bash
pnpm add @kitbase/analytics

Initialization

typescript
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

OptionTypeDefaultDescription
sdkKeystringrequiredYour SDK key from the Kitbase dashboard
debugbooleanfalseEnable debug logging to the console
baseUrlstring'https://api.kitbase.dev'API endpoint (override for self-hosted)
storageStorage | nulllocalStorageStorage backend for anonymous ID persistence
storageKeystring'kitbase_anonymous_id'Key used in storage for the anonymous ID

Analytics Options

OptionTypeDefaultDescription
autoTrackPageViewsbooleantrueAutomatically track page views on navigation
trackBfcacheRestorebooleantrueTrack 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.
autoTrackOutboundLinksbooleantrueAutomatically track clicks on external links
autoTrackClicksbooleantrueAutomatically track clicks on interactive elements
autoTrackScrollDepthbooleantrueAutomatically track maximum scroll depth per page
autoTrackVisibilitybooleantrueTrack element visibility via data-kb-track-visibility attributes
autoTrackWebVitalsbooleanfalseTrack Core Web Vitals (LCP, CLS, INP, FCP, TTFB)
autoDetectFrustrationbooleantrueDetect frustration signals (rage clicks and dead clicks)

Offline Options

OptionTypeDefaultDescription
enabledbooleanfalseEnable offline queue
maxQueueSizenumber1000Maximum number of events to queue
flushIntervalnumber30000Auto-flush interval in milliseconds
flushBatchSizenumber50Number of events per flush batch
maxRetriesnumber3Maximum retry attempts for failed events
retryBaseDelaynumber1000Base delay in milliseconds for exponential backoff

API Reference

Event Tracking

track(options: TrackOptions): Promise<TrackResponse>

Track a custom event.

typescript
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.

typescript
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.

typescript
await kitbase.trackRevenue({
  amount: 99.99,
  currency: 'USD',
  orderId: 'order-123',
  productId: 'prod-456',
  tags: { plan: 'premium' },
});

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.

typescript
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.

typescript
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.

typescript
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.

typescript
const userId = kitbase.getUserId();
// => "user-123" or null

reset(): void

Reset the user identity. Clears the identified user ID, generates a new anonymous ID, and starts a new session.

typescript
kitbase.reset();

Session Management

getSession(): SessionInfo

Get the current session information.

typescript
const session = kitbase.getSession();
// => {
//   id: 'sess_abc123',
//   startedAt: 1705321800000,
//   lastActivityAt: 1705323600000
// }

getSessionId(): string

Get the current session ID.

typescript
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.

typescript
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.

typescript
kitbase.registerOnce({
  first_seen: new Date().toISOString(),
});

unregister(key: string): void

Remove a single super property.

typescript
kitbase.unregister('platform');

getSuperProperties(): Record<string, unknown>

Get all currently registered super properties.

typescript
const props = kitbase.getSuperProperties();
// => { app_version: '2.1.0', environment: 'production' }

clearSuperProperties(): void

Remove all super properties.

typescript
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.

typescript
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.

typescript
kitbase.cancelTimeEvent('Checkout Completed');

getTimedEvents(): string[]

Get a list of all events that currently have active timers.

typescript
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.

typescript
const elapsed = kitbase.getEventDuration('Video Watched');
// => 45000

Offline 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.

typescript
const stats = await kitbase.getQueueStats();
// => { pending: 15, failed: 2, total: 17 }

flushQueue(): Promise<void>

Force send all queued events immediately.

typescript
await kitbase.flushQueue();

clearQueue(): Promise<void>

Clear all queued events without sending them.

typescript
await kitbase.clearQueue();

Debug & Lifecycle

setDebugMode(enabled: boolean): void

Toggle debug logging at runtime.

typescript
kitbase.setDebugMode(true);

isDebugMode(): boolean

Check whether debug mode is currently enabled.

typescript
if (kitbase.isDebugMode()) {
  console.log('Debug mode is active');
}

enableAutoPageViews(): void

Enable automatic page view tracking after initialization.

typescript
kitbase.enableAutoPageViews();

shutdown(): Promise<void>

Flush all pending events, clean up timers and listeners, and release resources. Call this when your application unmounts.

typescript
await kitbase.shutdown();

TypeScript Types

TrackOptions

typescript
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

typescript
interface TrackResponse {
  /** Unique event ID (e.g., "evt_abc123") */
  id: string;
  /** Event name */
  event: string;
  /** ISO 8601 timestamp */
  timestamp: string;
}

PageViewOptions

typescript
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

typescript
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

typescript
interface OutboundLinkOptions {
  /** Full URL of the outbound link */
  url: string;
  /** Link text content */
  text?: string;
}

IdentifyOptions

typescript
interface IdentifyOptions {
  /** Unique user identifier */
  userId: string;
  /** User properties (email, name, plan, etc.) */
  traits?: Record<string, unknown>;
}

SessionInfo

typescript
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

typescript
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:

typescript
import {
  KitbaseError,
  AuthenticationError,
  ValidationError,
  TimeoutError,
  ApiError,
} from '@kitbase/analytics';

KitbaseError

Base class for all SDK errors.

typescript
class KitbaseError extends Error {
  message: string;
}

AuthenticationError

Thrown when the API key is invalid or missing.

typescript
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.

typescript
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.

typescript
class TimeoutError extends KitbaseError {
  message: string; // "Request timed out"
}

ApiError

Thrown when the API returns an error response (4xx or 5xx).

typescript
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

typescript
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

typescript
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

typescript
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

typescript
'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 };
typescript
// 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

typescript
// 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 };
typescript
// 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');
vue
<!-- 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

typescript
// plugins/kitbase.client.ts
import { init } from '@kitbase/analytics';

export default defineNuxtPlugin(() => {
  const kitbase = init({ sdkKey: 'YOUR_SDK_KEY' });

  return {
    provide: {
      kitbase,
    },
  };
});
vue
<!-- 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       Done

Enabling Offline Mode

typescript
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:

AttemptDelayAction
1st retry1 secondRetry
2nd retry2 secondsRetry
3rd retry4 secondsRetry
After max retries--Event dropped

Storage

EnvironmentBackendPersistence
BrowserIndexedDB via DexieEvents survive page reloads and browser restarts
Node.jsIn-memoryEvents are lost on process restart

Queue Management

typescript
// 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

typescript
// 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:

EventConfig FlagTrigger
screen_viewautoTrackPageViewsPage load, pushState, popstate
screen_viewtrackBfcacheRestorePage restored from bfcache (back/forward in MPAs)
outbound_linkautoTrackOutboundLinksClick on external link
clickautoTrackClicksClick on interactive element
scroll_depthautoTrackScrollDepthPage unload (reports max scroll %)
rage_clickautoDetectFrustration3+ clicks within 1s in same area
dead_clickautoDetectFrustrationClick with no DOM change within 1s
web_vitalsautoTrackWebVitalsOnce 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:

html
<!-- 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.

typescript
// lib/kitbase.ts
import { init } from '@kitbase/analytics';

export const kitbase = init({ sdkKey: 'YOUR_SDK_KEY' });
typescript
// 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:

ChannelUse Case
paymentsSubscriptions, purchases, refunds
usersSignups, logins, profile updates
errorsApplication errors, exceptions
featuresFeature usage tracking
engagementGeneral user interactions
systemDeployments, 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:

typescript
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:

typescript
// 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:

typescript
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

typescript
// Good
tags: { user_type: 'premium', error_code: 'E001' }

// Bad
tags: { UserType: 'premium', 'error-code': 'E001' }

Next Steps

Released under the MIT License.