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-otp-autofill

v1.1.0

Published

Automatically read and auto-fill OTP SMS verification codes in Expo and React Native apps on Android, with zero SMS permissions. Typed wrapper around Google's SMS Retriever API.

Readme

expo-otp-autofill

Automatically read and auto-fill OTP SMS codes in Expo and React Native apps on Android — with zero SMS permissions.

A thin, typed Expo module around Google's official SMS Retriever API. Your app receives only the one verification SMS addressed to it, so you never request RECEIVE_SMS or READ_SMS and never trip Play Store's restricted-permission review.

npm version npm downloads platform: Android license: MIT

| | | |---|---| | Platforms | Android (functional) · iOS & Web (safe no-op) | | Permissions | None | | Requires | Google Play Services · an 11-char app hash appended to your SMS | | Exports | useOtpAutoFill · extractOtp · getAppHashAsync · startSmsRetrieverAsync · stopSmsRetrieverAsync · addListener |


Requirements

The SMS Retriever API only delivers a message to your app if the SMS ends with your app's 11-character hash. Get the hash with getAppHashAsync() and have your SMS provider append it:

Your verification code is 123456.

AB12cd3Efgh

The hash is derived from your package name and signing certificate, so debug builds, release builds, and Play App Signing each produce a different one. Read it at runtime rather than hard-coding it.

Installation

npx expo install expo-otp-autofill

This adds a native Play Services dependency, so rebuild:

npx expo prebuild --clean
npx expo run:android

Not compatible with Expo Go — use a development build.

Quick start

import { TextInput } from 'react-native';
import { useOtpAutoFill } from 'expo-otp-autofill';

export default function VerifyScreen() {
  const { otp, message, clear } = useOtpAutoFill({ length: 6 });

  return (
    <TextInput
      value={otp ?? ''}
      placeholder="Enter the 6-digit code"
      keyboardType="number-pad"
      textContentType="oneTimeCode" // iOS QuickType autofill
      autoComplete="sms-otp"        // Android autofill hint
    />
  );
}

Listening starts on mount and re-arms automatically after every message, so consecutive codes (e.g. after "resend") are captured without extra work.


API

useOtpAutoFill(options?)

React hook. Starts the retriever on mount, parses the incoming SMS, and cleans up on unmount.

Options

| Option | Type | Default | Description | |---|---|---|---| | length | number \| [min, max] | [4, 8] | Number of digits to extract. | | timeout | number | 30000 | Milliseconds before otp auto-resets to null. 0 disables auto-clearing. |

Returns

| Property | Type | Description | |---|---|---| | otp | string \| null | The extracted code, e.g. "123456". | | message | string \| null | The raw SMS body, including the app hash. | | clear | () => void | Resets otp and message. Listening continues. |

extractOtp(text, options?)

Pure function — no native module, works on every platform. Returns string | null.

extractOtp('Your code is 6451. df3Wz2qP', { length: 4 }); // "6451"

Patterns are tried most-specific first, so an unrelated number earlier in the message cannot win:

  1. Keyword then codeOTP: 1234, Your OTP is 1234, verification code is 123456
  2. Code then keyword123456 is your verification code
  3. Any standalone digit run of the right length, as a last resort
extractOtp('Order #987654 confirmed. Your OTP is 1234'); // "1234", not "987654"

Recognised keywords: otp, passcode, password, code, pin, verification, token.

It never throws. Non-string text returns null; an out-of-range, reversed, or non-numeric length falls back to [4, 8].

getAppHashAsync()

Promise<string> — the 11-character hash to append to your SMS. Returns "" on iOS and web.

const hash = await getAppHashAsync(); // "AB12cd3Efgh"

startSmsRetrieverAsync() / stopSmsRetrieverAsync()

Imperative control if you'd rather not use the hook. start opens a 5-minute listening window in Play Services and resolves true once armed; stop unregisters the receiver.

await startSmsRetrieverAsync(); // Promise<boolean>
await stopSmsRetrieverAsync();  // Promise<void>

addListener(eventName, listener)

Subscribe directly to native events. Returns an EventSubscription with .remove().

const sub = addListener('onOtpReceived', ({ message }) => console.log(message));
sub.remove();

| Event | Payload | Fired when | |---|---|---| | onOtpReceived | { message: string } | A matching SMS arrives. | | onOtpError | { message: string } | The 5-minute window expires with no SMS. |

Exported types: OtpLength, EventSubscription, ExpoOtpAutofillModuleEvents.


Platform support

The SMS Retriever API is Android-only. On iOS and web every export is a safe no-op, so you can call useOtpAutoFill() unconditionally in shared code — as the Rules of Hooks require — without platform branching or crashes.

| API | Android | iOS / Web | |---|---|---| | useOtpAutoFill() | Listens for the OTP SMS | { otp: null, message: null, clear }; never touches native code | | getAppHashAsync() | 11-char hash | "" | | startSmsRetrieverAsync() | true once armed | false | | stopSmsRetrieverAsync() | Unregisters the receiver | No-op | | extractOtp() | Works | Works — pure JS |

iOS needs no library. The OS surfaces one-time codes in the QuickType bar when a field opts in. One TextInput covers both platforms:

<TextInput textContentType="oneTimeCode" autoComplete="sms-otp" keyboardType="number-pad" />

FAQ

Does this require SMS permissions? No. It requests no permissions at all. Google Play Services filters the SMS and hands your app only the message ending in its app hash.

Can it read arbitrary or historical SMS? No. It only ever sees a message that ends with your app hash and arrives while the 5-minute window is open. For general SMS access, see expo-sms-listener (requires RECEIVE_SMS).

Why is no OTP detected? Most often the SMS is missing the app hash, or the hash belongs to a different build variant. Log await getAppHashAsync() in the exact build you're testing and compare it to the trailing characters of the SMS.

Does it work in Expo Go? No. It contains native code — use npx expo prebuild and a development build.

Does it work on iOS or the web? It runs without crashing but does nothing. See Platform support. Use textContentType="oneTimeCode" on iOS.

Does it work on Android devices without Google Play Services? No. The SMS Retriever API is part of Play Services.

Can it catch more than one code? Yes. The hook re-arms the listener after every message and after the 5-minute timeout.


Related

expo-sms-listener — reads any SMS from any sender, including while the app is closed. Requires the RECEIVE_SMS permission. Use it when you don't control the server sending the message.

License

MIT © MULERx