npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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/expo

Optional 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 prebuild

2. 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 specify
  • CFBundleURLTypes -- Custom URL schemes for deep linking

iOS (Entitlements):

  • com.apple.developer.associated-domains -- Associated domains for Universal Links (auto-prefixed with applinks:)

Android (AndroidManifest.xml):

  • Intent filters on .MainActivity for 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:

  1. Creates a LayersReactNative instance
  2. Calls init() with AsyncStorage persistence
  3. Optionally requests ATT permission via expo-tracking-transparency
  4. Optionally sets up deep link listening via expo-linking
  5. Forwards runtime errors (from track/screen/flush) to onError
  6. 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