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

react-native-permission-handler

v0.8.2

Published

Smart permission UX flows for React Native — pre-prompts, blocked handling, settings redirect & foreground re-check. Pluggable engine: works with react-native-permissions, Expo, or your own.

Downloads

1,451

Readme

react-native-permission-handler

Smart permission UX flows for React Native. Pre-prompts, blocked handling, settings redirect, and foreground re-check — in one hook, one component, and a pluggable engine. Works with react-native-permissions, Expo modules, or any custom backend.

📚 Full documentation →

import { PermissionGate } from "react-native-permission-handler";
import { Permissions } from "react-native-permission-handler/rnp";

export function QRScannerScreen() {
  return (
    <PermissionGate
      permission={Permissions.CAMERA}
      prePrompt={{ title: "Camera", message: "We need your camera to scan QR codes." }}
      blockedPrompt={{ title: "Camera blocked", message: "Enable camera in Settings." }}
      fallback={<Spinner />}
    >
      <QRScanner />
    </PermissionGate>
  );
}

That's the whole flow: pre-prompt modal, system dialog, blocked recovery, Settings round-trip, and AppState re-check on return. No state machine to wire up, no foreground listener to juggle.

Why this library

  • Pure state machine at the core. 12 states, pure transitions, no React or native code underneath. Every hook and component is a thin side-effect layer on top.
  • Pluggable engines. Bring your own permissions backend. Zero-config with react-native-permissions, auto-discovery with Expo modules, plus createTestingEngine for unit tests and createNoopEngine for web/Storybook.
  • Declarative components, RN primitives only. PermissionGate, DefaultPrePrompt, DefaultBlockedPrompt, and LimitedUpgradePrompt — no third-party UI dependencies, override any of them with a render prop.

Installation

npm install react-native-permission-handler

Then pick a permissions backend:

# Option A: react-native-permissions (recommended, auto-detected)
npm install react-native-permissions

# Option B: Expo modules (auto-discovered from installed expo-* packages)

# Option C: custom — implement the PermissionEngine interface yourself

With react-native-permissions installed, the library auto-creates an engine the first time a hook runs. Zero config needed.

Quick start

With PermissionGate (declarative)

import { PermissionGate } from "react-native-permission-handler";
import { Permissions } from "react-native-permission-handler/rnp";

<PermissionGate
  permission={Permissions.CAMERA}
  prePrompt={{ title: "Camera", message: "We need your camera." }}
  blockedPrompt={{ title: "Blocked", message: "Enable in Settings." }}
  fallback={<Spinner />}
>
  <CameraView />
</PermissionGate>

With usePermissionHandler (imperative)

import { usePermissionHandler } from "react-native-permission-handler";
import { Permissions } from "react-native-permission-handler/rnp";

function CameraScreen() {
  const camera = usePermissionHandler({
    permission: Permissions.CAMERA,
    prePrompt: { title: "Camera", message: "We need your camera." },
    blockedPrompt: { title: "Blocked", message: "Enable in Settings." },
    onGrant: () => analytics.track("camera_granted"),
  });

  if (camera.isChecking) return <Spinner />;
  if (camera.isGranted) return <CameraView />;
  if (camera.isUnavailable) return <Text>Camera not available.</Text>;
  return null; // default pre-prompt / blocked modals render on top
}

The state machine

The core of the library is a pure state machine that drives the entire permission flow:

                          +-------+
                          | idle  |
                          +---+---+
                              |
                           CHECK
                              |
                        +-----v-----+
                        | checking  |
                        +-----+-----+
                              |
              +---------------+---------------+
              |               |               |
           GRANTED         DENIED          BLOCKED
              |               |               |
        +-----v---+    +-----v-----+   +-----v-------+
        | granted |    | prePrompt |   | blockedPrompt|
        +---------+    +-----+-----+   +------+------+
                             |                 |
                    +--------+--------+   OPEN_SETTINGS
                    |                 |        |
              CONFIRM            DISMISS  +----v----------+
                    |                 |   | openingSettings|
              +-----v-----+    +-----v+  +----+----------+
              | requesting |   |denied|       |
              +-----+------+   +------+  SETTINGS_RETURN
                    |                         |
           +--------+--------+      +---------v-----------+
           |        |        |      |recheckingAfterSettings|
        GRANTED  DENIED   BLOCKED   +---------+-----------+
           |        |        |                |
     +-----v-+ +---v--+ +---v--------+   +---+---+
     |granted| |denied| |blockedPrompt|   |granted| or back
     +-------+ +------+ +------------+   +-------+ to blockedPrompt

limited is a sibling of granted — iOS 14+ partial photo access. isGranted is true for both, but isLimited lets you surface an upgrade prompt. See the full state list in docs/api/types.md.

Core APIs

Every API is documented in depth under docs/api/.

  • usePermissionHandler — single-permission hook. Full lifecycle: check, pre-prompt, request, blocked recovery, settings round-trip, optional requestFullAccess for limited → granted upgrade.
  • useMultiplePermissions — sequential or parallel multi-permission orchestration. Per-permission handlers, stable id keys, resume() after a Settings trip, blockedPermissions summary.
  • PermissionGate — declarative component. renderPrePrompt, renderBlockedPrompt, renderDenied, and renderLimited render props.
  • transition(state, event) — raw pure state machine function. Build your own hook or integrate with a state library.

Engines

An engine is the pluggable adapter between this library and the actual permissions backend. See docs/api/engines.md for the full reference, including resolution order (engine prop > setDefaultEngine() > auto RNP fallback).

  • createRNPEngine({ normalizePhotoLibrary?, normalizeAndroid? })react-native-permissions adapter with opt-in Android and photo-library status normalization.
  • createExpoEngine() — Expo modules adapter. Zero config, auto-discovers installed expo-camera, expo-location, expo-notifications, etc.
  • createTestingEngine(initialStatuses?, { autoGrantUnset? }) — controllable engine for unit tests. Defaults are symmetric: both check() and request() return "denied" for unseeded permissions. Pass { autoGrantUnset: true } for a happy-path shortcut that auto-grants on request().
  • createNoopEngine(defaultStatus?) — always-granted stub for web builds and Storybook.
  • Any object implementing PermissionEngine — roll your own.

Recipes

Drop-in solutions to real problems. See docs/recipes/.

Platform gotchas

iOS:

  • Before shipping to the App Store, make sure your app declares a PrivacyInfo.xcprivacy at the target level. See the iOS Privacy Manifest guide for a boilerplate template and the full list of permission usage-description keys.
  • The system permission dialog only shows once per permission, ever. Once denied via the system dialog, there is no programmatic path back — only Settings. Always show a pre-prompt first to warm the user up.
  • check() returns denied for both "never asked" and "denied once" — both are still requestable.
  • limited is an iOS 14+ state for photo library partial access. isGranted is true for it (backward compatible), isLimited distinguishes it.
  • App Tracking Transparency (APP_TRACKING_TRANSPARENCY) can only be requested while the app is in the .active state. Calling it from a mount effect or cold-start path often fires while the app is still .inactive, and iOS silently returns denied with no system prompt — the user never gets a chance to allow tracking. Defer the request until after your first user interaction, or gate it on an AppState listener that waits for active. Rule of thumb: never call request("ios.permission.APP_TRACKING_TRANSPARENCY") from useEffect(..., []) on a screen the user is navigating to for the first time.

Android:

  • After 2 denials, Android 11+ auto-blocks the permission. No more system dialogs.
  • checkNotifications() never returns blocked on Android 13+ — the RNP engine handles this internally. Enable normalizeAndroid: true to also cache the last request() result for accurate check() reads.
  • "One-time" permission grants (location, camera, mic) auto-revoke after ~30–60s of backgrounding.
  • Android 16 (API 36+) hang. request() can hang indefinitely when a permission is in never_ask_again state (facebook/react-native#53887). The library auto-applies a 5 s requestTimeout default on Android 16 and routes to the blocked prompt on expiry. Override with an explicit requestTimeout per-hook if you need different behavior.

Notifications: pass "notifications" as the permission identifier. The RNP engine routes to checkNotifications/requestNotifications. For Expo, map "notifications" to expo-notifications in the engine config (or rely on auto-discovery).

What this library doesn't cover

The library wraps device permission prompts — the OS-level CAMERA, PHOTO_LIBRARY, LOCATION, etc. dialogs. It deliberately does not cover the following adjacent problems:

  • HealthKit / Google Fit / Apple Health — these use per-data-type authorization with a fundamentally different model (no "denied" signal is returned to the app by design). Use react-native-health, @kingstinct/react-native-healthkit, or native modules.
  • OAuth and social login scopes — Google Sign-In, Facebook Login, "Sign in with Apple," etc. These are auth grants, not device permissions. Use @react-native-google-signin/google-signin or the provider-specific SDK.
  • IAP / StoreKit entitlements — use react-native-iap.
  • Push notification provider tokens (APNs / FCM registration) — this library handles the user-facing notification permission. Device-token registration and routing belong to @react-native-firebase/messaging or similar.
  • HomeKit, Local Network, NFC, Contacts groups, Gamekit, CarPlay — domain-specific permission models. Some map cleanly via a custom engine; most are better served by dedicated libraries.

If you're unsure whether your use case fits, a good test: does your permission prompt show a system-standard "Allow / Don't Allow" dialog? If yes, the library probably fits. If it's a custom auth flow or a per-scope consent screen, look elsewhere.

What's new in v0.7.0

  • requestFullAccess() on the hook result — upgrade from limited → granted without leaving the app. Engine-routed, throws with a clear error if unsupported.
  • renderLimited on PermissionGate — custom UI during iOS 14+ partial photo access.
  • Permissions.BUNDLESBLUETOOTH, LOCATION_BACKGROUND, and CALENDARS_WRITE_ONLY presets that resolve to the correct string[] per platform and OS version.
  • MultiPermissionEntry.id — stable cross-platform keys for statuses/handlers records.
  • resume() on useMultiplePermissions — restart a stopped sequential flow from current ungranted statuses, preserving already-granted progress.
  • skipPrePrompt: boolean | "android" — one-tap composer flows without a pre-prompt modal, safely scoped to Android only.
  • Optional prePrompt / blockedPrompt config — custom-UI users no longer need dummy configs to satisfy types.
  • Android 16 auto-recovery — 5 s default requestTimeout on API 36+, routed to the blocked prompt on expiry.
  • createRNPEngine({ normalizeAndroid, normalizePhotoLibrary }) — opt-in Android status normalization (pre-13 POST_NOTIFICATIONS, dialog-dismiss misreports, stale check cache) and iOS photo-library unavailable → blocked rewrite.

See docs/api/ and docs/recipes/ for full details.

Requirements

  • React Native >= 0.76
  • React >= 18
  • One of:
    • react-native-permissions >= 4.0.0 (auto-detected, zero config)
    • Expo permission modules (use createExpoEngine)
    • Custom PermissionEngine implementation

Contributing

Issues and PRs welcome. The project uses Biome for linting, Vitest for tests, and tsup for builds. Run npm test && npm run lint && npm run typecheck before submitting a PR.

License

MIT