Analytics usage
Use trackEvent to send typed product events from your screens and hooks.
Complete Analytics setup first. App code imports from
@/services/analytics; the PostHog SDK is confined to the analytics service and
AnalyticsProvider.
What’s already tracked
The event names and properties are defined in src/services/analytics/events.ts.
| Event | Properties | When it is sent |
|---|---|---|
onboarding_completed | notifications?: 'enabled' | 'skipped' | Onboarding completes and its answers are saved. notifications is absent when the app has no notifications step. |
sign_up_completed | method: 'email', needs_email_confirmation: boolean | Supabase accepts the sign-up request. |
sign_in_completed | method: 'email' | 'apple' | 'google' | Sign-in succeeds. |
signed_out | None | The sign-out hook completes. |
account_deleted | None | The delete-account function succeeds. |
chat_message_sent | model: string, has_image: boolean, new_thread: boolean | The chat SDK calls onFinish for a pending message. |
image_generated | model: string, style: string, aspect_ratio: string | Image generation succeeds. |
image_generation_blocked | reason: 'pro_required' | Image generation returns pro_required. |
scan_completed | None | A scan succeeds. |
ai_consent_granted | version: number | The user taps Allow on the AI consent card and the account records it. |
ai_consent_withdrawn | version: number | The user withdraws AI consent from Profile. |
paywall_shown | placement: 'onboarding_end' | 'quota_exceeded' | A paywall was on screen for the placement. |
paywall_skipped | placement: 'onboarding_end' | 'quota_exceeded', reason from the table below | A placement request ended without a paywall on screen. |
pro_status_changed | is_pro: boolean | The RevenueCat provider observes a change in Pro status after its initial value. |
Every presentPaywall call sends exactly one of paywall_shown and
paywall_skipped, so placement requests are the sum of the two. reason says
why nothing appeared:
| Reason | Sent when |
|---|---|
unconfigured | The RevenueCat SDK is not configured on this platform. |
not_presented | RevenueCat did not present its paywall. |
error | RevenueCat reported an error, or either presenter, RevenueCat or Superwall, threw. |
holdout | The user is in the Superwall campaign’s holdout group. |
no_audience_match | The user matches no Superwall campaign audience. |
paywall_not_available | Superwall had no paywall it could show. |
placement_not_found | No Superwall campaign contains the placement, or Superwall returned a result the app does not recognise. |
Without Superwall, quota_exceeded opens the in-app quota sheet, which counts as
shown because it is the paywall for that placement.
notifications: 'enabled' records the onboarding button the user chose. It does
not confirm that the operating system granted permission.
After sign-in, SessionProvider identifies the user with their Supabase ID and
email. Product events omit emails, prompts, message text, images and file paths.
Adding events
Define the event
Add a property to the existing AnalyticsEvents type:
export type AnalyticsEvents = {
// Keep the existing events.
report_exported: { format: 'pdf' | 'csv' };
};Use snake_case for event and property names. Keep user-entered content out of the properties.
Track the completed action
Call the helper after the export succeeds:
import { trackEvent } from '@/services/analytics';
trackEvent('report_exported', { format: 'pdf' });TypeScript checks the event name and its property shape. An event with no
properties uses undefined in the type and takes no second argument.
Verify
Run yarn typecheck, perform the action, then find report_exported in
PostHog and check its format property.
If no event arrives, confirm that both EXPO_PUBLIC_POSTHOG_API_KEY and
EXPO_PUBLIC_POSTHOG_HOST are set. A missing value disables analytics without an
error.
Screen views
The app does not call analytics.screen() explicitly. To track route changes,
follow Expo Router’s screen-tracking pattern
in a component mounted under the analytics provider:
import { useEffect } from 'react';
import { usePathname } from 'expo-router';
import { analytics } from '@/services/analytics';
export function ScreenTracking() {
const pathname = usePathname();
useEffect(() => {
analytics.screen(pathname);
}, [pathname]);
return null;
}Check that a route change produces one screen event. Avoid adding another tracker if you have already configured screen tracking elsewhere.
Swapping the provider
Write an adapter
Create src/services/analytics/<vendor>.adapter.ts implementing
AnalyticsPort. Use posthog.adapter.ts as the example.
Select the adapter
Update src/services/analytics/analytics.ts to create your adapter when the
vendor is configured and use noopAnalytics otherwise. Keep the exports in
index.ts so existing imports continue to work.
Update the provider and lint rules
Replace or remove the PostHog-specific wrapper in
src/provider/AnalyticsProvider.tsx. If the new vendor needs a provider,
mount it there.
In eslint.config.js, update vendorOwners and the restricted SDK imports
so the new SDK is confined to its integration files.
Verify
Run yarn typecheck and yarn lint. Sign in and confirm that
sign_in_completed arrives in the new service with method: 'email'.