Stop churn in the first 2 weeks. Funnel tracking + session replay for onboarding flows.
Account-first analytics. Buffer events pre-auth, flush on identify. Session replay with privacy controls. Built for B2B SaaS.
npm i onbored-jsVanilla JS
import { onbored } from 'onbored-js';
onbored.init({ projectKey: 'pk_live_...' });
onbored.identifyAccount('acct_123', { plan: 'enterprise' });
onbored.funnel('onboarding');
onbored.step('profile-setup', { slug: 'onboarding' });
onbored.complete({ slug: 'onboarding' });React
import { OnboredProvider, useFunnel } from 'onbored-js/react';
function App() {
return (
<OnboredProvider config={{ projectKey: 'pk_live_...', accountId: 'acct_123' }}>
<OnboardingFlow />
</OnboredProvider>
);
}
function OnboardingFlow() {
const { step, complete } = useFunnel('onboarding');
return (
<button onClick={() => step('setup').then(complete)}>
Done
</button>
);
}Events appear at onbored.io instantly.
Account-First Architecture
Events buffer until identifyAccount() called. Required for B2B multi-tenant tracking.
Funnels Conversion journeys (e.g., onboarding, checkout). Each gets UUID flow ID.
onbored.funnel('signup', { source: 'landing' }); // Returns flow IDSteps Milestones within funnels. Track completions or skips.
onbored.step('email-verified', { slug: 'signup', method: 'magic-link' });
onbored.skip('team-setup', { slug: 'signup', reason: 'solo' });Sessions Auto-managed. 30min timeout. Persists in localStorage. Rotates on account switch.
Initialize SDK. Call before other methods.
| Option | Type | Description | Required |
|---|---|---|---|
projectKey |
string |
Project key | ✓ |
accountId |
string |
Account identifier | |
accountTraits |
object |
Account metadata | |
userId |
string |
User identifier | |
userTraits |
object |
User metadata | |
debug |
boolean |
Console logging | |
env |
string |
Skip API calls | |
sessionReplay |
object |
See Session Replay section |
Identify account. Flushes buffered events. Rotates session on change.
onbored.identifyAccount('acct_789', { plan: 'enterprise', seats: 50 });Identify user. Optional. Merges traits.
onbored.identify('user_101', { email: 'user@co.com', role: 'admin' });Start funnel. Returns flow ID (UUID).
onbored.funnel('signup', { source: 'google' });Track step completion.
onbored.step('email-verified', { slug: 'signup', method: 'link' });Track step skip.
onbored.skip('team-invite', { slug: 'signup', reason: 'solo' });Complete funnel. Flushes immediately.
onbored.complete({ slug: 'signup', duration: 420 });Custom events.
onbored.capture('feature_used', { feature: 'export', format: 'csv' });Logout. Clears session + stops replay.
onbored.reset();Cleanup. Removes listeners.
onbored.destroy();Initializes SDK. Client-side only (use 'use client' in Next.js App Router).
import { OnboredProvider } from 'onbored-js/react';
<OnboredProvider config={{ projectKey: 'pk_live_...', accountId: 'acct_123' }}>
<App />
</OnboredProvider>Next.js App Router
// app/providers.tsx
'use client';
import { OnboredProvider } from 'onbored-js/react';
export function Providers({ children }) {
return (
<OnboredProvider config={{ projectKey: 'pk_...', accountId: 'acct_...' }}>
{children}
</OnboredProvider>
);
}
// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }) {
return <html><body><Providers>{children}</Providers></body></html>;
}Auto-starts funnel on mount. Returns { step, skip, complete }.
import { useFunnel } from 'onbored-js/react';
function Wizard() {
const { step, complete } = useFunnel('onboarding');
return <button onClick={() => step('done').then(complete)}>Finish</button>;
}Record user sessions. GDPR-friendly. Gzip compression. Auto-pause on idle.
onbored.init({
projectKey: 'pk_...',
sessionReplay: {
apiHost: 'https://api.onbored.com',
flushInterval: 10000, // Default: 10s
maskInputs: true, // Default: true
maskInputOptions: {
password: true, // Always masked
email: true, // Always masked
tel: true, // Always masked
text: false, // Configurable
},
blockElements: ['.cc-form'], // CSS selectors
privateSelectors: ['[data-ssn]'], // Additional privacy
},
});Auto-masked inputs: password, email, tel
Auto-blocked elements: [data-private], [data-sensitive], .private, .sensitive, .ssn, .credit-card
- Size: >900KB
- Event count: >100
- Time: Every 10s (configurable)
- Page hidden
- Session stop
On identifyAccount() call:
- Stop current recording
- Rotate session
- Restart with new account context
- Emit
replay_stopped+replay_started
onbored.init({
projectKey: 'pk_...',
storage: {
sessionStorageKey: 'custom-session',
activityStorageKey: 'custom-activity',
flowContextStorageKey: 'custom-flow',
},
});onbored.init({
projectKey: 'pk_...',
global: { headers: { Authorization: 'Bearer token' } },
});No API calls. Console logging. Manual flush via window.__onboredFlush().
onbored.init({ projectKey: 'pk_...', env: 'development', debug: true });Full type definitions included.
import type { OnboredClientOptions, EventPayload } from 'onbored-js';import { OnboredProvider, useFunnel } from 'onbored-js/react';
<OnboredProvider config={{
projectKey: 'pk_...',
accountId: org.id,
accountTraits: { plan: org.plan, seats: org.seats },
userId: user.id,
sessionReplay: { apiHost: 'https://api.onbored.com' },
}}>
<Wizard />
</OnboredProvider>
function Wizard() {
const { step, skip, complete } = useFunnel('setup');
return (
<>
<button onClick={() => step('profile')}>Profile</button>
<button onClick={() => skip('team', { reason: 'solo' })}>Skip Team</button>
<button onClick={() => complete()}>Done</button>
</>
);
}// Before user authenticates
onbored.init({ projectKey: 'pk_...' });
onbored.funnel('signup'); // Buffered
onbored.step('email-entered', { slug: 'signup' }); // Buffered
// After auth
onbored.identifyAccount('acct_123'); // Flushes buffered events
onbored.step('verified', { slug: 'signup' });
onbored.complete({ slug: 'signup' });<div data-onbored-step="welcome" data-onbored-funnel="onboarding">
Welcome! (tracked via IntersectionObserver at 50% visibility)
</div>Queue Strategy
- In-memory queue: 5s flush or 100 events
- Retry: Exponential backoff, max 5 attempts
- Unload:
sendBeacon()fallback - Complete: Immediate flush
Session Management
- 30min timeout
- UUID-based, localStorage
- Auto-rotation on account switch
- Page navigation tracked (SPA support)
Funnel Context
- UUID flow ID per funnel
- sessionStorage persistence
- Auto page view tracking
- IntersectionObserver for step views (50% threshold)
Events not appearing
- Check
env: 'production'(dev mode skips API) - Verify
projectKeyat onbored.io - Enable
debug: true, check console - Network tab: Look for
/ingest/events
React hook errors
- Ensure
<OnboredProvider>wraps components - Use
'use client'in Next.js App Router
Session not persisting
- Check localStorage availability
- Incognito mode may block storage
Funnel context lost
- sessionStorage required
- Same
projectKeyacross pages - Call
funnel()before navigation
git clone https://github.com/MohamedH1998/onbored-js.git && cd onbored-js
pnpm install
pnpm dev # Watch mode
pnpm test # Unit tests
pnpm test:e2e # E2E (Playwright)
pnpm build # Build for productionReleases: Changesets + GitHub Actions
pnpm changeset→ describe change- Commit changeset file
- Merge to
main→ auto-publish to npm
Pre-release stages: alpha → beta → rc → stable (currently alpha)
Website • npm • GitHub • Issues
Support: info@onbored.io • @momito
MIT © Onbored