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

expo-esim-utils

v0.1.3

Published

Native eSIM detection and installation for Expo and React Native. Detect eSIM support, list active plans, and install eSIM profiles via Apple Universal Link (iOS 17.4+) and EuiccManager (Android). No carrier entitlement required.

Readme

expo-esim-utils

Native eSIM detection and installation for Expo and React Native. Detect eSIM support, read active cellular plans, and install eSIM profiles — all from JavaScript.

iOS uses Apple's Universal Link (iOS 17.4+) for one-tap eSIM installation and CoreTelephony for detection. Android uses EuiccManager.downloadSubscription() for direct profile download with a system consent dialog.

No carrier entitlement required. Works for any eSIM reseller or MVNO app.

Features

| Feature | iOS | Android | |---|---|---| | Detect eSIM support | iOS 12+ | Android 9+ | | Get detailed capability info | iOS 12+ | Android 9+ | | List active cellular plans | iOS 12+ | Android 5.1+ | | Install eSIM profile | iOS 17.4+ | Android 9+ |

How eSIM Installation Works

Building an eSIM app? Here's what you need to know:

  • iOS 17.4+: Apple provides a Universal Link (esimsetup.apple.com) that opens the native eSIM installation screen. No special entitlement needed — this is how Airalo, Holafly, and other major eSIM apps work.
  • iOS < 17.4: No entitlement-free install API exists. The module returns "unsupported" so you can show a QR code or manual activation code as fallback.
  • Android 9+: Uses EuiccManager.downloadSubscription() which shows a system consent dialog. The user confirms and the profile downloads directly. Falls back to opening the eSIM settings screen with the activation code on the clipboard.

Note: CTCellularPlanProvisioning.addPlan() requires Apple's carrier entitlement (com.apple.CommCenter.fine-grained) which is only granted to MNOs (mobile network operators). If you're a reseller or MVNO, that API won't work for you. This module uses the Universal Link approach instead.

Installation

npx expo install expo-esim-utils

Or with npm/yarn:

npm install expo-esim-utils
# or
yarn add expo-esim-utils

Requirements

  • Expo SDK 50+
  • iOS 12.0+ (detection), iOS 17.4+ (installation)
  • Android API 28+ (Android 9+)
  • Requires a development build — will not work in Expo Go

After installing, rebuild your app:

# iOS
npx expo run:ios

# Android
npx expo run:android

API

isEsimSupported()

Check if the device supports eSIM. Synchronous — returns instantly.

import { isEsimSupported } from 'expo-esim-utils';

if (isEsimSupported()) {
  console.log('This device supports eSIM!');
}

Returns: boolean

| Platform | Method | |---|---| | iOS 12+ | CTCellularPlanProvisioning.supportsCellularPlan() | | Android 9+ | EuiccManager.isEnabled() | | Web | Always returns false |


getEsimCapability()

Get detailed eSIM capability information including platform, support status, and active plans.

import { getEsimCapability } from 'expo-esim-utils';

const capability = getEsimCapability();
console.log(capability.isSupported); // true
console.log(capability.reason);      // "Device supports eSIM via CoreTelephony"
console.log(capability.activePlans); // [{ slot: "0", carrierName: "T-Mobile", ... }]

Returns: EsimCapability


getActivePlans()

Get a list of active cellular plans on the device.

import { getActivePlans } from 'expo-esim-utils';

const plans = getActivePlans();
for (const plan of plans) {
  console.log(`${plan.carrierName} (slot ${plan.slot})`);
  // "T-Mobile (slot 0)"
  // "Airalo (slot 1)" — isEmbedded: true (Android)
}

Returns: CellularPlan[]

Android: Requires READ_PHONE_STATE permission for full details on Android 10+. Returns an empty array without it (does not crash).


openEsimSetup(activationCode?)

Install an eSIM profile on the device. This is the main function for eSIM installation.

import { openEsimSetup } from 'expo-esim-utils';

const result = await openEsimSetup('LPA:1$smdp.example.com$ACTIVATION_CODE');

switch (result) {
  case 'settings_opened':
    // iOS: user is on the native eSIM install screen
    // Android: consent dialog shown or settings opened
    break;
  case 'success':
    // Android: profile downloaded successfully
    break;
  case 'unsupported':
    // iOS < 17.4: show QR code or manual fallback
    break;
  case 'fail':
    // Something went wrong
    break;
}

Parameters:

| Name | Type | Description | |---|---|---| | activationCode | string (optional) | eSIM activation code in LPA format: LPA:1$<SMDP_ADDRESS>$<MATCHING_ID> |

Returns: Promise<EsimSetupResult>

| Result | Meaning | |---|---| | "settings_opened" | Native eSIM install screen opened (iOS 17.4+ / Android) | | "success" | Profile downloaded successfully (Android only) | | "unsupported" | Direct install not available — show QR/manual fallback | | "fail" | Installation failed | | "unknown" | Result could not be determined |


Types

EsimCapability

type EsimCapability = {
  isSupported: boolean;
  platform: 'ios' | 'android';
  reason: string;
  osVersion?: string;            // Android: eUICC firmware version
  isSimPortAvailable?: boolean;  // Android 13+: whether a SIM port is free
  activePlans?: CellularPlan[];
};

CellularPlan

type CellularPlan = {
  slot: string;
  carrierName?: string;
  mobileCountryCode?: string;   // MCC
  mobileNetworkCode?: string;   // MNC
  isoCountryCode?: string;
  allowsVOIP?: boolean;         // iOS only
  subscriptionId?: number;      // Android only
  isEmbedded?: boolean;         // Android only: true if eSIM
};

EsimSetupResult

type EsimSetupResult = 'success' | 'fail' | 'unknown' | 'settings_opened' | 'unsupported';

Full Example

A typical eSIM installation screen with install button, QR code, and manual fallback:

import { useState } from 'react';
import { Alert, Button, Platform, View } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { isEsimSupported, openEsimSetup } from 'expo-esim-utils';

export function InstallEsim({ activationCode }: { activationCode: string }) {
  const [installing, setInstalling] = useState(false);

  const handleInstall = async () => {
    setInstalling(true);
    try {
      const result = await openEsimSetup(activationCode);

      if (result === 'settings_opened') {
        // User is on the native install screen — they'll return when done
      } else if (result === 'unsupported') {
        Alert.alert('Use QR Code', 'Direct install requires iOS 17.4+. Scan the QR code below.');
      } else if (result === 'success') {
        Alert.alert('Installed!', 'Your eSIM has been installed successfully.');
      } else {
        Alert.alert('Failed', 'Try using the QR code or manual method.');
      }
    } catch {
      Alert.alert('Error', 'eSIM installation is not available.');
    } finally {
      setInstalling(false);
    }
  };

  return (
    <View>
      <Button
        title={installing ? 'Installing...' : 'Install to Device'}
        onPress={handleInstall}
        disabled={installing}
      />

      {/* QR code fallback */}
      <QRCode value={activationCode} size={200} />
    </View>
  );
}

Comparison with Other Libraries

| Feature | expo-esim-utils | react-native-esim | react-native-sim-cards-manager | |---|---|---|---| | Expo Module (no linking) | Yes | No | No | | iOS install (no entitlement) | Yes (Universal Link) | No (requires carrier entitlement) | No (requires carrier entitlement) | | Android install | Yes (downloadSubscription) | No | Yes | | eSIM detection | Yes | Yes (iOS only) | Yes | | Active plan listing | Yes | No | Yes | | Maintained | Yes | No (2020) | Yes |

How It Works Under the Hood

iOS

| Function | Native API | |---|---| | isEsimSupported() | CTCellularPlanProvisioning.supportsCellularPlan() | | getEsimCapability() | CTCellularPlanProvisioning + CTTelephonyNetworkInfo | | getActivePlans() | CTTelephonyNetworkInfo.serviceSubscriberCellularProviders | | openEsimSetup() | UIApplication.open("https://esimsetup.apple.com/...") (iOS 17.4+) |

Android

| Function | Native API | |---|---| | isEsimSupported() | EuiccManager.isEnabled() | | getEsimCapability() | EuiccManager + EuiccInfo | | getActivePlans() | SubscriptionManager.getActiveSubscriptionInfoList() | | openEsimSetup() | EuiccManager.downloadSubscription() with consent dialog |

Contributing

Contributions are welcome! Please open an issue or pull request on GitHub.

License

MIT