@layers/expo
v2.1.0
Published
Layers Analytics Expo SDK — convenience wrapper with config plugin
Readme
Layers Expo SDK
@layers/expo is the Layers analytics SDK for Expo managed workflow projects. It wraps @layers/react-native and adds Expo-specific integrations: a config plugin for native setup automation, expo-tracking-transparency for ATT, expo-linking for deep links, expo-clipboard for clipboard attribution, and React context/hooks for idiomatic usage.
Use this package for Expo managed workflow projects. For bare React Native projects, use @layers/react-native instead.
Requirements
- Expo SDK 50.0.0+
- React Native 0.73.0+
- React 18.0+
Installation
npx expo install @layers/expoOptional Peer Dependencies
For full functionality, install these Expo packages:
npx expo install expo-tracking-transparency expo-linking expo-clipboard- expo-tracking-transparency -- ATT permission dialog (iOS)
- expo-linking -- Deep link handling
- expo-clipboard -- Clipboard attribution for deferred deep links (iOS)
Quick Start
1. Add the Config Plugin
In your app.config.js or app.json:
export default {
expo: {
plugins: [
[
'@layers/expo',
{
ios: {
attUsageDescription: 'We use this to show you relevant content.',
urlSchemes: ['myapp'],
associatedDomains: ['myapp.com']
},
android: {
intentFilters: [
{ scheme: 'myapp', host: 'open' },
{ scheme: 'https', host: 'myapp.com', pathPrefix: '/app' }
]
}
}
]
]
}
};Then run prebuild:
npx expo prebuild2. Initialize with the Provider
import { LayersProvider } from '@layers/expo';
export default function App() {
return (
<LayersProvider
config={{
appId: 'your-app-id',
environment: 'production'
}}
requestTracking={true}
enableDeepLinks={true}
onDeepLink={(data) => console.log('Deep link:', data.url)}
onError={(error) => console.error('Layers error:', error)}
>
<MyApp />
</LayersProvider>
);
}3. Use Hooks in Components
import { useLayersScreen, useLayersTrack } from '@layers/expo';
function SignupButton() {
const track = useLayersTrack();
return <Button title="Sign Up" onPress={() => track('signup_click', { source: 'hero' })} />;
}
function ProfileScreen() {
const screen = useLayersScreen();
useEffect(() => {
screen('Profile');
}, []);
return <View />;
}Config Plugin
The Layers Expo config plugin automates native project configuration via npx expo prebuild.
Plugin Options
interface LayersExpoPluginProps {
ios?: {
/** Custom ATT usage description for the permission dialog. */
attUsageDescription?: string;
/** URL schemes for deep linking (e.g., ['myapp']). */
urlSchemes?: string[];
/** Associated domains for Universal Links (e.g., ['myapp.com']). */
associatedDomains?: string[];
/** Additional SKAdNetwork identifiers to register. */
skAdNetworkIds?: string[];
/** Include default SKAdNetwork IDs (16 major ad networks). Default: true. */
includeDefaultSKAdNetworkIds?: boolean;
};
android?: {
/** Intent filters for deep linking. */
intentFilters?: Array<{
scheme: string; // e.g., 'myapp' or 'https'
host?: string; // e.g., 'myapp.com'
pathPrefix?: string; // e.g., '/app'
}>;
};
}What the Plugin Configures
iOS (Info.plist):
NSUserTrackingUsageDescription-- ATT usage description (defaults to a generic message if not provided)SKAdNetworkItems-- SKAdNetwork IDs from Meta, Google, TikTok, Snapchat, X, Unity, AppLovin, IronSource (16 default IDs) plus any custom IDs you specifyCFBundleURLTypes-- Custom URL schemes for deep linking
iOS (Entitlements):
com.apple.developer.associated-domains-- Associated domains for Universal Links (auto-prefixed withapplinks:)
Android (AndroidManifest.xml):
- Intent filters on
.MainActivityfor deep link and App Link handling android:autoVerify="true"is set automatically for HTTPS intent filters
Default SKAdNetwork IDs
The plugin includes 16 SKAdNetwork IDs by default from these networks:
- Meta/Facebook
- Google/YouTube
- TikTok
- Snapchat
- Twitter/X
- Unity Ads
- AppLovin
- IronSource
Set includeDefaultSKAdNetworkIds: false to disable defaults.
React Provider & Hooks
LayersProvider
import { LayersProvider } from '@layers/expo';
<LayersProvider
config={LayersRNConfig}
requestTracking?: boolean // Request ATT on mount. Default: false
enableDeepLinks?: boolean // Listen for deep links. Default: true
onDeepLink?: (data) => void // Deep link callback
onError?: (error) => void // Error callback (init + runtime)
>
{children}
</LayersProvider>The provider:
- Creates a
LayersReactNativeinstance - Calls
init()with AsyncStorage persistence - Optionally requests ATT permission via
expo-tracking-transparency - Optionally sets up deep link listening via
expo-linking - Forwards runtime errors (from track/screen/flush) to
onError - Shuts down the SDK on unmount
useLayers
function useLayers(): { isReady: boolean; layers: LayersReactNative | null };Access the SDK context. Returns isReady: false until initialization completes.
function MyComponent() {
const { isReady, layers } = useLayers();
if (!isReady) return <Text>Loading...</Text>;
return <Button title="Track" onPress={() => layers?.track('button_press')} />;
}useRequiredLayers
function useRequiredLayers(): LayersReactNative;Returns the SDK instance, throwing an error if not yet initialized or used outside a <LayersProvider>. Use when your component requires the SDK to be available.
function CheckoutButton() {
const layers = useRequiredLayers();
return (
<Button
title="Checkout"
onPress={() => layers.track('checkout_started', { cart_value: 49.99 })}
/>
);
}useLayersTrack
function useLayersTrack(): (eventName: string, properties?: EventProperties) => void;Returns a stable, memoized track function. No-op until the SDK initializes.
const track = useLayersTrack();
track('signup_click', { source: 'hero' });useLayersScreen
function useLayersScreen(): (screenName: string, properties?: EventProperties) => void;Returns a stable, memoized screen tracking function. No-op until the SDK initializes.
const screen = useLayersScreen();
useEffect(() => {
screen('Profile');
}, []);Imperative API
All exports from @layers/react-native are re-exported from @layers/expo. You can use the SDK without the provider:
import { LayersReactNative } from '@layers/expo';
const layers = new LayersReactNative({
appId: 'your-app-id',
environment: 'production'
});
await layers.init();
layers.track('event_name', { key: 'value' });
layers.screen('ScreenName');
layers.setAppUserId('user_123');
await layers.setUserProperties({ plan: 'premium' });
await layers.setConsent({ analytics: true, advertising: false });
await layers.flush();
layers.shutdown();See the @layers/react-native README for the full imperative API documentation.
ATT (App Tracking Transparency) -- iOS
Via Provider
Set requestTracking={true} on <LayersProvider> to automatically request ATT permission on mount.
Via Expo Functions
import { getExpoTrackingStatus, requestExpoTrackingPermission } from '@layers/expo';
// Request permission (auto-updates device info and consent on the Layers instance)
const status = await requestExpoTrackingPermission(layers);
// Returns: 'authorized' | 'denied' | 'restricted' | 'not_determined'
// Check current status without prompting
const currentStatus = await getExpoTrackingStatus();When layers is passed, requestExpoTrackingPermission automatically:
- Collects IDFA if authorized
- Updates device info with IDFA and ATT status
- Sets advertising consent based on the result
Via SDK Instance
const status = await layers.requestTrackingPermission();This also auto-detects expo-tracking-transparency and prefers it when available.
Deep Links
Via Provider
Set enableDeepLinks={true} (default) and provide an onDeepLink callback:
<LayersProvider
config={config}
enableDeepLinks={true}
onDeepLink={(data) => {
console.log('Deep link:', data.url);
navigation.navigate(data.path);
}}
>Via Expo Functions
import { parseDeepLink, setupExpoDeepLinkListener } from '@layers/expo';
const cleanup = await setupExpoDeepLinkListener((data) => {
console.log('Deep link:', data.url);
console.log('Source:', data.queryParams.utm_source);
});
// Later: cleanup()Auto-Tracking
The SDK automatically tracks deep_link_opened events (configurable via autoTrackDeepLinks in the config). This runs in addition to your onDeepLink callback.
Clipboard Attribution -- iOS
import { readExpoClipboardAttribution } from '@layers/expo';
const data = await readExpoClipboardAttribution();
if (data) {
console.log('Click URL:', data.clickUrl);
console.log('Click ID:', data.clickId);
}Uses expo-clipboard under the hood. The SDK's init() also reads clipboard attribution automatically when enabled by remote config.
Expo Router Integration
For automatic screen tracking with Expo Router:
import { useLayersExpoRouterTracking, useRequiredLayers } from '@layers/expo';
import { usePathname, useGlobalSearchParams } from 'expo-router';
function RootLayout() {
const layers = useRequiredLayers();
useLayersExpoRouterTracking(layers, usePathname, useGlobalSearchParams);
return <Stack />;
}SKAdNetwork (SKAN) -- iOS
SKAN is auto-configured from the server's remote config. No additional setup is required beyond including SKAdNetwork IDs in your config plugin (which is done by default).
Access the auto-configured SKAN manager:
const skanManager = layers.getSkanManager();
if (skanManager) {
const metrics = skanManager.getMetrics();
console.log('Current conversion value:', metrics.currentValue);
}For manual SKAN configuration, see the @layers/react-native documentation.
Full Export List
From @layers/react-native
// Core
(LayersReactNative, LayersError);
// Types
(LayersRNConfig,
ConsentState,
DeviceContext,
Environment,
EventProperties,
UserProperties,
DeepLinkData,
ClipboardAttribution,
SKANConversionRule,
SKANPresetConfig,
SKANMetrics,
ATTStatus);
// SKAN
SKANManager;
// ATT
(getATTStatus, requestTrackingAuthorization, isATTAvailable, getAdvertisingId, getVendorId);
// Deep Links
(parseDeepLink, setupDeepLinkListener);
// Utilities
(getOrSetInstallId, readClipboardAttribution, useLayersExpoRouterTracking);Expo-Specific
// Config plugin
withLayers (default export from '@layers/expo/plugin')
LayersExpoPluginProps
// ATT
requestExpoTrackingPermission, getExpoTrackingStatus
// Deep Links
setupExpoDeepLinkListener
// Clipboard
readExpoClipboardAttribution
// React
LayersProvider, LayersProviderProps,
useLayers, useRequiredLayers, useLayersTrack, useLayersScreen