# @thisbefine/analytics SDK - Complete Reference
> Lightweight analytics, error tracking, and logging for indie SaaS.
> Package: @thisbefine/analytics (~4KB gzipped)
> Entry points: @thisbefine/analytics, @thisbefine/analytics/react, @thisbefine/analytics/next
---
## Installation
```bash
npm install @thisbefine/analytics
# or
pnpm add @thisbefine/analytics
# or
yarn add @thisbefine/analytics
```
Prerequisites: Node.js 18+, React 18+ (for React/Next.js)
---
## Environment Variables
```bash
# Next.js
NEXT_PUBLIC_THISBEFINE_API_KEY=tif_your_key
NEXT_PUBLIC_TBF_API_KEY=tif_your_key # alternative
# Vite
VITE_THISBEFINE_API_KEY=tif_your_key
# Create React App
REACT_APP_THISBEFINE_API_KEY=tif_your_key
```
---
## Setup
### Next.js (App Router)
```tsx
// app/layout.tsx
import { Analytics } from "@thisbefine/analytics/next";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
### React SPA (Vite, CRA, etc.)
```tsx
// main.tsx or App.tsx
import { Analytics } from "@thisbefine/analytics/react";
function App() {
return (
<>
>
);
}
```
Note: React SPA requires manual page tracking (see Page Tracking section).
---
## Analytics Component Props
```tsx
```
---
## Configuration Object
```typescript
interface AnalyticsConfig {
apiKey: string; // REQUIRED
host?: string; // Default: "https://thisbefine.com"
flushAt?: number; // Default: 20 (events before auto-send)
flushInterval?: number; // Default: 10000 (ms between auto-send)
sessionTimeout?: number; // Default: 1800000 (30 min inactivity)
cookieDomain?: string; // For cross-subdomain tracking
debug?: boolean; // Default: false
respectDNT?: boolean; // Default: true
maxRetries?: number; // Default: 3
errors?: ErrorCaptureConfig;
}
interface ErrorCaptureConfig {
enabled?: boolean; // Default: true
captureConsoleErrors?: boolean; // Default: false
captureNetworkErrors?: boolean; // Default: false
maxBreadcrumbs?: number; // Default: 25
beforeSend?: (payload: ErrorPayload) => ErrorPayload | null;
}
```
### Example Configuration
```tsx
{
// Filter out specific errors
if (payload.message.includes("ResizeObserver")) return null;
return payload;
},
},
}}
/>
```
---
## Core Methods
### Event Tracking
```typescript
import { analytics } from "@thisbefine/analytics";
// Track custom events
analytics.track(eventName: string, properties?: Record);
// Examples
analytics.track("button_clicked", { buttonId: "signup", page: "landing" });
analytics.track("feature_used", { feature: "export", format: "csv" });
analytics.track("purchase_completed", { orderId: "123", amount: 99.99 });
```
### Page Tracking
```typescript
// Track page views (automatic in Next.js with trackPageviews={true})
analytics.page(name?: string, properties?: Record);
// Examples
analytics.page(); // Uses current URL
analytics.page("Dashboard");
analytics.page("Product Detail", { productId: "abc123" });
```
### User Identification
```typescript
// Link anonymous user to known identity
analytics.identify(userId: string, traits?: UserTraits);
// Examples
analytics.identify("user_123");
analytics.identify("user_123", {
email: "jane@example.com",
name: "Jane Doe",
avatar: "https://example.com/avatar.jpg",
plan: "pro",
createdAt: "2024-01-15",
});
```
### Account/Team Grouping (B2B)
```typescript
// Associate user with an account/team/organization
analytics.group(accountId: string, traits?: AccountTraits);
// Examples
analytics.group("account_456");
analytics.group("account_456", {
name: "Acme Inc",
plan: "enterprise",
mrr: 999,
industry: "Technology",
employeeCount: 50,
createdAt: new Date(),
});
```
### Reset (Logout)
```typescript
// CRITICAL: Call on logout to prevent data leakage
analytics.reset();
```
This clears: userId, user traits, accountId, account traits. Generates new anonymousId and sessionId.
### Get User State
```typescript
const user = analytics.getUser();
// Returns: { anonymousId, userId?, traits?, accountId?, accountTraits? }
```
---
## Error Tracking
### Capture Exceptions
```typescript
// Catch and report errors with context
analytics.captureException(error: Error, context?: Record);
// Example
try {
await processPayment(order);
} catch (error) {
analytics.captureException(error as Error, {
operation: "processPayment",
orderId: order.id,
amount: order.total,
});
throw error; // Re-throw or handle
}
```
### Capture Messages
```typescript
// Report errors without Error object
analytics.captureMessage(
message: string,
level?: "warning" | "error" | "fatal",
context?: Record
);
// Examples
analytics.captureMessage("Payment gateway timeout", "error", { gateway: "stripe" });
analytics.captureMessage("High memory usage detected", "warning", { usage: "95%" });
analytics.captureMessage("Database connection lost", "fatal");
```
### Breadcrumbs
```typescript
// Leave trail of events before crash
analytics.addBreadcrumb({
type: "click" | "navigation" | "network" | "console" | "custom",
message: string,
timestamp?: string,
data?: Record,
});
// Example
analytics.addBreadcrumb({
type: "click",
message: "Clicked checkout button",
data: { cartItems: 3 },
});
analytics.addBreadcrumb({
type: "network",
message: "POST /api/orders",
data: { status: 500 },
});
```
### Automatic Error Capture
The SDK automatically captures:
- Unhandled exceptions (throw new Error)
- Unhandled promise rejections
- window.onerror events
Optional (via config):
- console.error() calls (captureConsoleErrors: true)
- Failed fetch/XHR requests (captureNetworkErrors: true)
### Error Fingerprinting
Errors are grouped by fingerprint (message + top stack frame). Similar errors are deduplicated in the dashboard.
---
## Logging
```typescript
// Structured logs with levels
analytics.log(
message: string,
level: "debug" | "info" | "warn" | "error" | "fatal",
metadata?: Record
);
// Examples
analytics.log("User signed up", "info", { source: "google_oauth" });
analytics.log("Export completed", "info", { format: "csv", rows: 1000 });
analytics.log("Rate limit approaching", "warn", { current: 950, limit: 1000 });
analytics.log("Database query failed", "error", { query: "SELECT...", duration: 5000 });
analytics.log("Out of memory", "fatal", { heapUsed: "2GB" });
```
Log levels (severity order): debug < info < warn < error < fatal
Logs automatically include user context if identify() was called.
---
## React Hooks
Import from `@thisbefine/analytics/next` (Next.js) or `@thisbefine/analytics/react` (React SPA).
### Core Hooks
```typescript
import {
useAnalytics,
useTrack,
useIdentify,
useGroup,
usePage,
useReset,
useGetUser,
useCaptureException,
useLog,
} from "@thisbefine/analytics/next";
// Get full analytics instance
const analytics = useAnalytics();
// Get memoized track function for specific event
const trackClick = useTrack("button_clicked");
trackClick({ buttonId: "signup" });
// Get identify function
const identify = useIdentify();
identify("user_123", { email: "jane@example.com" });
// Get group function
const group = useGroup();
group("account_456", { name: "Acme Inc" });
// Get page tracking function
const page = usePage();
page("Dashboard");
// Get reset function
const reset = useReset();
reset(); // Call on logout
// Get current user state
const getUser = useGetUser();
const user = getUser();
// Get error capture function
const captureException = useCaptureException();
captureException(error, { context: "checkout" });
// Get log function
const log = useLog();
log("Action completed", "info", { action: "export" });
```
### Lifecycle Hooks
Pre-built hooks for common events:
```typescript
import {
// User lifecycle
useSignup,
useLogin,
useLogout,
useAccountDeleted,
// Subscription lifecycle
useSubscriptionStarted,
useSubscriptionCancelled,
useSubscriptionRenewed,
usePlanUpgraded,
usePlanDowngraded,
// Trial lifecycle
useTrialStarted,
useTrialEnded,
// Engagement
useInviteSent,
useInviteAccepted,
useFeatureActivated,
} from "@thisbefine/analytics/next";
// Examples
const signup = useSignup();
signup({ source: "landing_page", referrer: "google" });
const subscriptionStarted = useSubscriptionStarted();
subscriptionStarted({ plan: "pro", interval: "monthly", amount: 29 });
const featureActivated = useFeatureActivated();
featureActivated({ feature: "team_collaboration" });
```
### Hook Rules
1. Call hooks at the top level of your component
2. Don't call hooks conditionally
3. Requires `` component to be mounted
4. All hooks return memoized functions (safe for useEffect dependencies)
---
## Bug Report Widget
```tsx
import { BugReportFAB } from "@thisbefine/analytics/next";
```
The widget automatically captures:
- Current URL
- Browser info (user agent)
- OS and screen size
- User ID and anonymousId
- Timestamp
---
## Privacy Controls
### Opt Out/In
```typescript
// Stop all tracking (persists in localStorage)
analytics.optOut();
// Resume tracking
analytics.optIn();
// Check opt-out status
const isOptedOut = analytics.isOptedOut();
```
### Do Not Track
By default, the SDK respects the browser's Do Not Track setting (`respectDNT: true`). When DNT is enabled, no events are tracked.
To disable DNT respect (not recommended):
```tsx
```
---
## Data Flushing
Events are automatically batched and sent:
- When `flushAt` events are queued (default: 20)
- Every `flushInterval` milliseconds (default: 10000)
- On page unload (beforeunload event)
### Manual Flush
```typescript
// Force send all pending events (returns Promise)
await analytics.flush();
```
Use `flush()` when:
- Navigating to external URL
- Before critical operations
- In development for immediate feedback
---
## Types
### UserTraits
```typescript
interface UserTraits {
email?: string;
name?: string;
avatar?: string;
[key: string]: unknown; // Custom properties allowed
}
```
### AccountTraits
```typescript
interface AccountTraits {
name?: string;
plan?: string;
mrr?: number;
industry?: string;
employeeCount?: number;
createdAt?: string | Date;
[key: string]: unknown; // Custom properties allowed
}
```
### UserState
```typescript
interface UserState {
anonymousId: string; // Always present
userId?: string; // After identify()
traits?: UserTraits; // After identify() with traits
accountId?: string; // After group()
accountTraits?: AccountTraits;
}
```
### Event Payload
```typescript
interface EventPayload {
type: "track" | "identify" | "page" | "group";
timestamp: string; // ISO 8601
anonymousId: string;
userId?: string;
sessionId?: string;
accountId?: string;
context?: EventContext;
}
```
### Breadcrumb
```typescript
interface Breadcrumb {
type: "click" | "navigation" | "network" | "console" | "custom";
message: string;
timestamp?: string;
data?: Record;
}
```
### LogLevel
```typescript
type LogLevel = "debug" | "info" | "warn" | "error" | "fatal";
```
---
## Event Naming Conventions
### Pattern: `object_action`
Use snake_case consistently.
### Examples
```typescript
// User actions
"button_clicked"
"form_submitted"
"file_uploaded"
"search_performed"
// E-commerce
"product_viewed"
"cart_item_added"
"cart_item_removed"
"checkout_started"
"purchase_completed"
// SaaS
"feature_used"
"limit_reached"
"settings_updated"
"integration_connected"
// User journey
"onboarding_step_completed"
"onboarding_completed"
"activation_milestone"
```
---
## Common Patterns
### Login Flow
```typescript
const login = useLogin();
const identify = useIdentify();
const group = useGroup();
const handleLogin = async (credentials) => {
const user = await authenticateUser(credentials);
// Track login event
login({ method: "email" });
// Identify user
identify(user.id, {
email: user.email,
name: user.name,
plan: user.plan,
});
// Group into account (if B2B)
if (user.organizationId) {
group(user.organizationId, {
name: user.organizationName,
plan: user.organizationPlan,
});
}
};
```
### Logout Flow
```typescript
const logout = useLogout();
const reset = useReset();
const handleLogout = async () => {
// Track logout event
logout();
// CRITICAL: Reset identity before navigation
reset();
// Navigate to login page
router.push("/login");
};
```
### Form Tracking
```typescript
const trackSubmit = useTrack("form_submitted");
const handleSubmit = async (data) => {
trackSubmit({
formId: "contact",
fields: Object.keys(data).length,
hasAttachment: !!data.file,
});
await submitForm(data);
};
```
### Feature Usage
```typescript
const trackFeature = useTrack("feature_used");
const handleExport = async (format: string) => {
trackFeature({
feature: "export",
format,
recordCount: data.length,
});
await exportData(format);
};
```
### Error Boundary Integration
```typescript
class ErrorBoundary extends React.Component {
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
analytics.captureException(error, {
componentStack: errorInfo.componentStack,
errorBoundary: this.props.name,
});
}
}
```
### Manual Page Tracking (React SPA)
```typescript
// Required for non-Next.js apps
import { useLocation } from "react-router-dom";
import { usePage } from "@thisbefine/analytics/react";
function PageTracker() {
const location = useLocation();
const page = usePage();
useEffect(() => {
page(location.pathname, {
search: location.search,
hash: location.hash,
});
}, [location, page]);
return null;
}
// Add to app root
```
### Workspace/Team Context (B2B)
```typescript
const group = useGroup();
useEffect(() => {
if (workspace) {
group(workspace.id, {
name: workspace.name,
plan: workspace.plan,
memberCount: workspace.members.length,
createdAt: workspace.createdAt,
});
}
}, [workspace, group]);
```
---
## Local Storage Keys
All keys are prefixed with `tif_`:
```
tif_anonymous_id - Random ID for non-logged-in users
tif_user_id - User ID after identify()
tif_user_traits - User traits from identify()
tif_account_id - Account ID from group()
tif_account_traits - Account traits from group()
tif_session_id - Current session ID
tif_last_activity - For session timeout detection
tif_opt_out - User opt-out preference
```
---
## Common Pitfalls
### 1. Forgetting reset() on Logout
```typescript
// WRONG - next user's events attributed to previous user
const handleLogout = () => {
router.push("/login");
};
// CORRECT
const handleLogout = () => {
analytics.reset(); // Clear identity first
router.push("/login");
};
```
### 2. Calling identify() Too Late
```typescript
// WRONG - events before identify() not linked
router.push("/dashboard");
analytics.identify(user.id);
// CORRECT - identify immediately after auth
const user = await login();
analytics.identify(user.id, { email: user.email });
router.push("/dashboard");
```
### 3. SSR/Hydration Issues
```typescript
// WRONG - will error on server
const user = analytics.getUser();
// CORRECT - check for client-side
const user = typeof window !== "undefined" ? analytics.getUser() : null;
```
### 4. Multiple Analytics Instances
```typescript
// WRONG - will cause duplicate events
// Don't do this
// CORRECT - single instance at app root
```
### 5. Events Lost on External Navigation
```typescript
// WRONG - events may not send before navigation
window.location.href = "https://external.com";
// CORRECT - flush before external navigation
await analytics.flush();
window.location.href = "https://external.com";
```
### 6. Wrong API Key Format
```typescript
// WRONG
// CORRECT - must start with tif_
```
---
## Browser Support
- Chrome 80+
- Firefox 78+
- Safari 14+
- Edge 80+
- NOT supported: Internet Explorer
---
## Bundle Size
- Core SDK: ~3KB gzipped
- React integration: ~1KB gzipped
- Total: ~4KB gzipped
- Zero external dependencies
---
## API Endpoints
The SDK sends data to these endpoints:
```
POST /api/v1/track - Events and page views
POST /api/v1/identify - User identification
POST /api/v1/group - Account grouping
POST /api/v1/error - Error reports
POST /api/v1/bug-report - Bug report submissions
```
All endpoints are authenticated via the `tif_` API key in request headers.
---
## Security Notes
- API keys are safe to expose in client-side code (they can only send data, not read)
- Never log passwords, API keys, credit card numbers, SSNs in events/errors
- Use environment variables for API keys anyway (best practice)
- User data is isolated by workspace
---
## TypeScript
All types are exported from the main package:
```typescript
import type {
Analytics,
AnalyticsConfig,
ErrorCaptureConfig,
UserTraits,
AccountTraits,
UserState,
TrackEvent,
IdentifyEvent,
PageEvent,
GroupEvent,
ErrorPayload,
Breadcrumb,
LogLevel,
} from "@thisbefine/analytics";
```
---
## More Information
- Dashboard: https://thisbefine.com
- Documentation: https://thisbefine.com/docs