Web and mobile instrumentation playbook
Instrument outcomes before screens. A small shared event spine lets dashboards, CLI queries, and coding agents answer activation and release-impact questions without requiring every app to use the same UI.
The minimum product spine
Use stable event names for the lifecycle stages your product actually has:
| Stage | Example event |
|---|---|
| Acquisition | landing_page_view or page_view |
| Intent | aggregate:cta_clicked or signup_initiated |
| Account activation | auth_signup_succeeded |
| Workspace activation | project_created |
| Technical activation | sdk_initialized |
| First value | first_event_received and first_query_succeeded |
| Engagement | a product-specific outcome event |
| Monetization | checkout_started, purchase:success |
| Reliability | normalized crash or failure outcome |
| Agent value | agent:brief_generated, agent:recommendation_accepted |
Do not rename existing production events just to match this list. Add canonical events alongside legacy names, then migrate queries through an explicit alias plan.
Shared context
Keep dimensions bounded and useful across surfaces:
platform:web,ios, orandroid;projectSurface:landing,dashboard, orapp;runtimeEnv: attached by the SDK;appVersionandappBuildfor release comparisons;- stable flow identifiers such as
onboardingFlowId,paywallId, orexperimentVariant; - privacy-safe acquisition fields such as
utm_source,utm_medium,utm_campaign,referrer_host, andlanding_path.
Never send emails, phone numbers, names, tokens, secrets, complete URLs containing personal query parameters, receipts, or payment credentials as event properties.
Web setup
import { init } from '@analyticscli/sdk';
const analytics = init({
apiKey: import.meta.env.PUBLIC_ANALYTICSCLI_PUBLISHABLE_API_KEY,
platform: 'web',
projectSurface: 'dashboard',
identityTrackingMode: 'consent_gated',
initialConsentGranted: false,
initialFullTrackingConsentGranted: false,
});
Open event collection and persistent identity only when the host application’s consent decision allows it:
analytics.setConsent(true);
analytics.setFullTrackingConsent(true);
For a consentless marketing counter, use aggregate mode in the website integration. Aggregate events receive a new unlinkable ID per event. They support page and CTA counts, but not unique people, user funnels, or retention.
Expo and React Native setup
import * as Application from 'expo-application';
import { Platform } from 'react-native';
import { init } from '@analyticscli/sdk';
const analytics = init({
apiKey: process.env.EXPO_PUBLIC_ANALYTICSCLI_PUBLISHABLE_API_KEY,
debug: typeof __DEV__ === 'boolean' ? __DEV__ : false,
platform: Platform.OS,
projectSurface: 'app',
appVersion: Application.nativeApplicationVersion ?? undefined,
identityTrackingMode: 'consent_gated',
dedupeOnboardingStepViewsPerSession: true,
dedupeScreenViewsPerSession: true,
});
Prefer the scoped onboarding and paywall trackers so flow IDs and step indexes stay consistent. Track terminal outcomes such as complete, skip, purchase success, purchase cancel, and purchase failure—not only impressions.
Verify before expanding
Use Debug to prove transport and schema:
analyticscli --include-debug schema events --last 24h
Then verify real Release traffic:
analyticscli agent brief --last 14d
analyticscli generic --metric event_count --group-by eventName --last 14d
Only after the spine is visible should you add deeper screen or feature events. Every new event should answer a named product question, have an owner, and define how long its data remains useful.
Release-impact loop
For each shipped change:
- Record the release version and deployment time.
- Define the primary event, guardrail metric, and expected direction.
- Compare the same metric and segment before and after release.
- Check crash and revenue context before assigning causality.
- Keep the change reversible until the signal is stable.
This same loop works for a landing-page CTA, dashboard onboarding, an Expo paywall, or an AI-agent workflow.