Installation
npm install @outlit/browser
Quick Start
Wrap your app with OutlitProvider and use the useOutlit hook in components.
If your app has user authentication, pass the user identity via the user prop. This is the recommended approach as it automatically handles login/logout transitions.// app/providers.tsx (Client Component)
'use client'
import { OutlitProvider } from '@outlit/browser/react'
import { useUser } from '@clerk/nextjs' // or useAuth0, useSession, etc.
export function Providers({ children }: { children: React.ReactNode }) {
const { user, isLoaded } = useUser()
// Build user identity from your auth provider
const outlitUser = isLoaded && user ? {
email: user.primaryEmailAddress?.emailAddress,
userId: user.id,
traits: {
name: user.fullName,
}
} : null
return (
<OutlitProvider
publicKey="pk_your_key"
trackPageviews
user={outlitUser}
>
{children}
</OutlitProvider>
)
}
// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
For marketing sites without authentication, just add the provider:// app/layout.tsx (Next.js App Router)
import { OutlitProvider } from '@outlit/browser/react'
export default function RootLayout({ children }) {
return (
<html>
<body>
<OutlitProvider publicKey="pk_your_key" trackPageviews>
{children}
</OutlitProvider>
</body>
</html>
)
}
Using the Hook
Track custom events with the useOutlit hook:
// components/pricing-button.tsx
'use client'
import { useOutlit } from '@outlit/browser/react'
export function PricingButton() {
const { track } = useOutlit()
return (
<button onClick={() => track('pricing_clicked', { plan: 'pro' })}>
View Pricing
</button>
)
}
Provider Configuration
OutlitProvider
The OutlitProvider initializes tracking and provides context to child components.
import { OutlitProvider } from '@outlit/browser/react'
<OutlitProvider
publicKey="pk_xxx"
apiHost="https://app.outlit.ai"
trackPageviews={true}
trackForms={true}
trackEngagement={true}
formFieldDenylist={['custom_secret_field']}
flushInterval={5000}
>
{children}
</OutlitProvider>
Props
Your organization’s public key.
apiHost
string
default:"https://app.outlit.ai"
API endpoint for sending events.
Automatically track pageviews on route changes.
Automatically capture form submissions.
Additional field names to exclude from form capture.
How often to flush queued events (ms).
Whether to start tracking automatically. Set to false to wait for user consent. Use enableTracking() from the useOutlit hook after consent.
Automatically identify users when they submit forms containing an email field. Extracts email and name (first name, last name, full name) using field name heuristics. Set to false to disable and call identify() manually. See Auto-Identify for details.
Track engagement metrics (active time on page). Emits engagement events on page exit and navigation.
Idle timeout in milliseconds for engagement tracking. After this period of no user interaction, the user is considered idle.
Track booking events from calendar embeds (Cal.com, Calendly).
Current user identity. When provided with email or userId, identifies the user. When null or undefined, clears the user identity. This is the recommended way to handle user identity in server-rendered apps.
User Identity Patterns
The user prop is the recommended way to handle user identity. It’s simpler and more reliable than manual identify() calls because:
- Automatic lifecycle management: Login/logout transitions are handled automatically
- No timing issues: Identity is set before any events are tracked
- Cleaner code: No need for useEffect + identify() synchronization
Pattern 1: Client-Side Auth (Clerk, Auth0, etc.)
For auth libraries that provide React hooks:
// app/providers.tsx
'use client'
import { OutlitProvider } from '@outlit/browser/react'
import { useUser } from '@clerk/nextjs' // or useAuth0, useSession, etc.
export function Providers({ children }: { children: React.ReactNode }) {
const { user, isLoaded } = useUser()
const outlitUser = isLoaded && user ? {
email: user.primaryEmailAddress?.emailAddress,
userId: user.id,
traits: { name: user.fullName }
} : null
return (
<OutlitProvider publicKey="pk_xxx" user={outlitUser}>
{children}
</OutlitProvider>
)
}
Pattern 2: Server-Side Auth (NextAuth, Lucia, etc.)
For auth systems where session is available server-side:
// app/layout.tsx (Server Component)
import { OutlitProvider } from '@outlit/browser/react'
import { auth } from '@/auth' // Your auth library
export default async function RootLayout({ children }) {
const session = await auth()
return (
<html>
<body>
<OutlitProvider
publicKey={process.env.NEXT_PUBLIC_OUTLIT_KEY!}
user={session?.user ? {
email: session.user.email,
userId: session.user.id,
traits: { name: session.user.name }
} : null}
>
{children}
</OutlitProvider>
</body>
</html>
)
}
Which pattern should I use?
- Client-side auth hooks (Clerk, Auth0): Use Pattern 1 with a client providers component
- Server-side sessions (NextAuth, Lucia, custom JWT): Use Pattern 2 directly in the layout
Both patterns work equally well - choose based on how your auth library provides user data.
When the user prop changes (login/logout), the provider automatically calls setUser() or clearUser() internally. You don’t need to call these methods manually.
Consent Management
If you need to wait for user consent before tracking:
// app/layout.tsx
import { OutlitProvider } from '@outlit/browser/react'
export default function RootLayout({ children }) {
return (
<OutlitProvider publicKey="pk_xxx" autoTrack={false}>
{children}
</OutlitProvider>
)
}
// components/cookie-banner.tsx
'use client'
import { useOutlit } from '@outlit/browser/react'
export function CookieBanner() {
const { enableTracking, isTrackingEnabled } = useOutlit()
// Don't show if already tracking (user previously accepted)
if (isTrackingEnabled) return null
return (
<div className="fixed bottom-4 left-4 right-4 bg-white p-4 shadow-lg rounded-lg">
<p>We use cookies to improve your experience.</p>
<div className="flex gap-2 mt-2">
<button
onClick={enableTracking}
className="px-4 py-2 bg-indigo-600 text-white rounded"
>
Accept
</button>
<button className="px-4 py-2 border rounded">
Decline
</button>
</div>
</div>
)
}
With Third-Party CMPs
'use client'
import { useOutlit } from '@outlit/browser/react'
import { useEffect } from 'react'
export function CookiebotListener() {
const { enableTracking } = useOutlit()
useEffect(() => {
const handleConsent = () => {
// @ts-ignore - Cookiebot global
const consent = window.Cookiebot?.consent
if (consent?.marketing || consent?.analytics) {
enableTracking()
}
}
window.addEventListener('CookiebotOnAccept', handleConsent)
return () => window.removeEventListener('CookiebotOnAccept', handleConsent)
}, [enableTracking])
return null
}
'use client'
import { useOutlit } from '@outlit/browser/react'
import { useEffect } from 'react'
export function OneTrustListener() {
const { enableTracking } = useOutlit()
useEffect(() => {
// @ts-ignore - OneTrust global
const groups = window.OnetrustActiveGroups || ''
// C0002 = Performance, C0004 = Targeting/Marketing
if (groups.includes('C0004') || groups.includes('C0002')) {
enableTracking()
}
// Also listen for changes
const handleConsent = () => {
// @ts-ignore
const updatedGroups = window.OnetrustActiveGroups || ''
if (updatedGroups.includes('C0004') || updatedGroups.includes('C0002')) {
enableTracking()
}
}
window.addEventListener('consent.onetrust', handleConsent)
return () => window.removeEventListener('consent.onetrust', handleConsent)
}, [enableTracking])
return null
}
Hooks
useOutlit
The primary hook for tracking events and identifying users.
import { useOutlit } from '@outlit/browser/react'
function MyComponent() {
const {
track,
identify,
setUser,
clearUser,
activate,
engaged,
paid,
churned,
getVisitorId,
isInitialized,
isTrackingEnabled,
enableTracking,
} = useOutlit()
// Track an event
track('button_clicked', { buttonId: 'cta' })
// Identify the user
identify({ email: 'user@example.com', traits: { plan: 'pro' } })
// Get visitor ID (null if tracking not enabled)
const visitorId = getVisitorId()
// Check if tracking is active
if (!isTrackingEnabled) {
return <button onClick={enableTracking}>Accept Tracking</button>
}
}
Returns
track
(eventName: string, properties?: object) => void
Track a custom event.
identify
(options: IdentifyOptions) => void
Identify the current visitor.
setUser
(identity: UserIdentity) => void
Set the current user identity. Ideal for SPA authentication flows.
Clear the current user identity (on logout).
activate
(properties?: object) => void
Mark the current user as activated.
engaged
(properties?: object) => void
Mark the current user as engaged.
paid
(properties?: object) => void
Mark the current user as paid.
churned
(properties?: object) => void
Mark the current user as churned.
Get the current visitor’s ID. Returns null if tracking is not enabled.
Whether tracking is currently enabled. Will be false if autoTrack is false and enableTracking() hasn’t been called.
Enable tracking. Call this after obtaining user consent. Only needed if autoTrack is false.
Whether the tracker is ready.
useTrack
A convenience hook that returns just the track function:
import { useTrack } from '@outlit/browser/react'
function FeatureButton({ featureId }) {
const track = useTrack()
return (
<button onClick={() => track('feature_clicked', { featureId })}>
Try Feature
</button>
)
}
useIdentify
A convenience hook that returns just the identify function:
import { useIdentify } from '@outlit/browser/react'
function LoginForm() {
const identify = useIdentify()
const onLogin = async (email, password) => {
const user = await login(email, password)
identify({
email: user.email,
userId: user.id,
traits: {
name: user.name,
plan: user.plan
}
})
}
}
Journey Stage Events
Track user progression through your product lifecycle:
'use client'
import { useOutlit } from '@outlit/browser/react'
function OnboardingComplete() {
const { activate } = useOutlit()
const handleComplete = () => {
activate({ flow: 'onboarding', step: 'completed' })
}
return <button onClick={handleComplete}>Complete Setup</button>
}
// After a successful payment
function PaymentSuccess({ plan, amount }) {
const { paid } = useOutlit()
useEffect(() => {
paid({ plan, amount })
}, [plan, amount, paid])
return <div>Payment successful!</div>
}
Stage methods (activate, engaged, paid, churned) require the user to be identified first. Use setUser(), identify(), or the user prop before calling stage methods.
Framework Examples
Next.js App Router
// app/layout.tsx
import { OutlitProvider } from '@outlit/browser/react'
export default function RootLayout({ children }) {
return (
<html>
<body>
<OutlitProvider
publicKey={process.env.NEXT_PUBLIC_OUTLIT_KEY!}
trackPageviews
>
{children}
</OutlitProvider>
</body>
</html>
)
}
// app/dashboard/page.tsx
'use client'
import { useOutlit } from '@outlit/browser/react'
import { useEffect } from 'react'
export default function Dashboard() {
const { track } = useOutlit()
useEffect(() => {
track('dashboard_viewed')
}, [track])
return <div>Dashboard</div>
}
Next.js Pages Router
// pages/_app.tsx
import { OutlitProvider } from '@outlit/browser/react'
export default function App({ Component, pageProps }) {
return (
<OutlitProvider publicKey={process.env.NEXT_PUBLIC_OUTLIT_KEY!}>
<Component {...pageProps} />
</OutlitProvider>
)
}
Remix
// app/root.tsx
import { OutlitProvider } from '@outlit/browser/react'
export default function App() {
return (
<html>
<body>
<OutlitProvider publicKey={window.ENV.OUTLIT_KEY}>
<Outlet />
</OutlitProvider>
</body>
</html>
)
}
Common Patterns
Track on Mount
Track when a component mounts (e.g., page viewed):
import { useOutlit } from '@outlit/browser/react'
import { useEffect } from 'react'
function ProductPage({ productId }) {
const { track } = useOutlit()
useEffect(() => {
track('product_viewed', { productId })
}, [productId, track])
return <div>Product {productId}</div>
}
Track custom form events:
import { useOutlit } from '@outlit/browser/react'
function ContactForm() {
const { track, identify } = useOutlit()
const handleSubmit = (e) => {
e.preventDefault()
const formData = new FormData(e.target)
track('contact_form_submitted', {
subject: formData.get('subject')
})
identify({
email: formData.get('email'),
traits: {
name: formData.get('name'),
company: formData.get('company')
}
})
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<input name="name" type="text" />
<input name="company" type="text" />
<input name="subject" type="text" />
<button type="submit">Send</button>
</form>
)
}
Identify After Auth
Prefer the user prop instead. The pattern below works, but using the user prop on OutlitProvider is cleaner and handles the lifecycle automatically. See User Identity Patterns above.
If you can’t use the user prop (e.g., the provider is in a different part of the tree), you can sync manually:
import { useOutlit } from '@outlit/browser/react'
import { useUser } from '@/hooks/use-user' // Your auth hook
function AuthObserver() {
const { setUser, clearUser } = useOutlit()
const { user, isLoading } = useUser()
useEffect(() => {
if (isLoading) return
if (user) {
setUser({
email: user.email,
userId: user.id,
traits: {
name: user.name,
plan: user.subscription?.plan
}
})
} else {
clearUser()
}
}, [user, isLoading, setUser, clearUser])
return null
}
TypeScript
Full TypeScript support is included:
import {
useOutlit,
OutlitProvider,
type OutlitProviderProps,
type UseOutlitReturn,
type UserIdentity,
} from '@outlit/browser/react'
// Types are inferred
const { track, identify, setUser } = useOutlit()
// Or explicitly typed
const outlit: UseOutlitReturn = useOutlit()
// User identity type
const user: UserIdentity = {
email: 'user@example.com',
userId: 'usr_123',
traits: { name: 'Jane' }
}
Next Steps