Skip to main content

Installation

npm install @outlit/browser

Quick Start

Wrap your app with OutlitProvider and use the useOutlit hook in components.

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

publicKey
string
required
Your organization’s public key.
apiHost
string
default:"https://app.outlit.ai"
API endpoint for sending events.
trackPageviews
boolean
default:"true"
Automatically track pageviews on route changes.
trackForms
boolean
default:"true"
Automatically capture form submissions.
formFieldDenylist
string[]
Additional field names to exclude from form capture.
flushInterval
number
default:"5000"
How often to flush queued events (ms).
autoTrack
boolean
default:"true"
Whether to start tracking automatically. Set to false to wait for user consent. Use enableTracking() from the useOutlit hook after consent.
autoIdentify
boolean
default:"true"
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.
trackEngagement
boolean
default:"true"
Track engagement metrics (active time on page). Emits engagement events on page exit and navigation.
idleTimeout
number
default:"30000"
Idle timeout in milliseconds for engagement tracking. After this period of no user interaction, the user is considered idle.
trackCalendarEmbeds
boolean
default:"true"
Track booking events from calendar embeds (Cal.com, Calendly).
user
UserIdentity | null
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.
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
}

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.
clearUser
() => void
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.
getVisitorId
() => string | null
Get the current visitor’s ID. Returns null if tracking is not enabled.
isTrackingEnabled
boolean
Whether tracking is currently enabled. Will be false if autoTrack is false and enableTracking() hasn’t been called.
enableTracking
() => void
Enable tracking. Call this after obtaining user consent. Only needed if autoTrack is false.
isInitialized
boolean
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 Form Submission

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