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-manager

v0.2.5

Published

React Native permissions helper for Android and iOS — check, request, ensure, hooks, and a PermissionGate.

Readme

react-native-permission-manager

npm version CI TypeScript React Native License: MIT

Cross-platform permissions for React Native (Android + iOS). One API for check / request / ensure, plus hooks and a small gate component. TurboModule when available, old bridge as fallback.

Table of contents

Features

  • PermissionManager.request('camera') and friends — same call on both platforms
  • Android rationale dialog before the system prompt (optional / customizable)
  • usePermission() for status + request / ensure / settings
  • <PermissionGate> — render kids only when granted
  • Refreshes when the app comes back from Settings
  • Groups: requestGroup('media') or pass your own list
  • Typed permission names and results
  • Camera, mic, photos, notifications, location, contacts, calendar, bluetooth, storage, SMS, call phone

Requirements

  • React Native 0.73+ (0.79+ recommended)
  • Node 18+

Installation

npm install react-native-permission-manager
# or
yarn add react-native-permission-manager

Rebuild the native app after install (npx pod-install on iOS, then run Android/iOS).

iOS (Info.plist)

Add a usage string for every permission you request. Missing keys cause iOS to abort the app (abort_with_payload) when you call request() — the library detects this and returns UNAVAILABLE with a console warning instead of crashing, but real prompts only appear after you add the keys:

<!-- Camera / Mic / Photos -->
<key>NSCameraUsageDescription</key>
<string>Camera access is needed to take photos.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is needed for audio.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo library access is needed to pick images.</string>

<!-- Contacts -->
<key>NSContactsUsageDescription</key>
<string>Contacts access is needed to find friends.</string>

<!-- Location (foreground) -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location access is needed to show nearby content.</string>

<!-- Location (background / always) — also keep the WhenInUse key above -->
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Background location is needed for continuous updates.</string>
<!-- Optional legacy: NSLocationAlwaysUsageDescription -->

<!-- Calendar (iOS 17+ prefers FullAccess) -->
<key>NSCalendarsUsageDescription</key>
<string>Calendar access is needed to manage events.</string>
<key>NSCalendarsFullAccessUsageDescription</key>
<string>Calendar access is needed to manage events.</string>

<!-- Bluetooth -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth access is needed to connect to devices.</string>
cd ios && pod install && cd ..

Android (AndroidManifest.xml)

The library does not inject permissions (avoids Manifest Merger conflicts).
Declare only what your app uses:

<!-- Camera / Mic / Notifications -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<!-- Contacts -->
<uses-permission android:name="android.permission.READ_CONTACTS" />

<!-- Location -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Background location (Android 10+) — also declare Fine/Coarse above -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<!-- Photos / Storage (API 33+) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Older devices -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

<!-- Calendar / Bluetooth / Phone (as needed) -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.CALL_PHONE" />

| Friendly name | Android permission(s) | iOS Info.plist key | |---------------|----------------------|--------------------| | camera | CAMERA | NSCameraUsageDescription | | microphone | RECORD_AUDIO | NSMicrophoneUsageDescription | | photos | READ_MEDIA_IMAGES + READ_MEDIA_VIDEO (API 33+) | NSPhotoLibraryUsageDescription | | notification | POST_NOTIFICATIONS (API 33+) | (none — system prompt) | | contacts | READ_CONTACTS | NSContactsUsageDescription | | location | ACCESS_FINE_LOCATION + ACCESS_COARSE_LOCATION | NSLocationWhenInUseUsageDescription | | locationBackground | ACCESS_BACKGROUND_LOCATION | NSLocationAlwaysAndWhenInUseUsageDescription | | calendar | READ_CALENDAR + WRITE_CALENDAR | NSCalendarsUsageDescription / FullAccess | | bluetooth | BLUETOOTH_SCAN + BLUETOOTH_CONNECT (API 31+) | NSBluetoothAlwaysUsageDescription | | sms | READ_SMS | (unavailable on iOS) | | callPhone | CALL_PHONE | (unavailable on iOS) |

Usage

import {
  PermissionManager,
  PermissionProvider,
  PermissionGate,
  usePermission,
} from 'react-native-permission-manager';

1. Imperative (PermissionManager)

const result = await PermissionManager.check('camera');
// { type, status, canAskAgain }

await PermissionManager.request('camera', {
  title: 'Camera',
  message: 'Needed for your profile photo',
});

await PermissionManager.ensure('camera'); // request → Settings if blocked
await PermissionManager.openSettings();

// Permission groups — built-in name or an ad-hoc list
const { allGranted, results } = await PermissionManager.requestGroup('media');
await PermissionManager.checkGroup(['camera', 'microphone']);

2. Hook (usePermission)

function CameraButton() {
  const { status, granted, loading, request, ensure, openSettings } =
    usePermission('camera', {
      title: 'Camera',
      message: 'Needed for profile photo',
    });

  if (loading) return null;
  if (granted) return <OpenCamera />;

  return (
    <Button
      title={status === 'BLOCKED' ? 'Open Settings' : 'Allow Camera'}
      onPress={() => (status === 'BLOCKED' ? openSettings() : request())}
    />
  );
}

3. Provider (recommended at app root)

function App() {
  return (
    <PermissionProvider watchedPermissions={['camera', 'microphone', 'notification']}>
      <RootNavigator />
    </PermissionProvider>
  );
}

Caches statuses and refreshes when the app returns from Settings.

4. PermissionGate

<PermissionGate
  permission="camera"
  autoRequest
  renderFallback={({ ensure, openSettings }) => (
    <>
      <Button title="Grant camera" onPress={() => ensure()} />
      <Button title="Open Settings" onPress={() => openSettings()} />
    </>
  )}
>
  <CameraScreen />
</PermissionGate>

5. Permission Groups

Bundle related permissions and check/request them together, using a built-in group name or your own list:

import { usePermissionGroup } from 'react-native-permission-manager';

function MediaScreen() {
  const { results, allGranted, anyGranted, loading, request } = usePermissionGroup('media');
  // 'media' = ['camera', 'microphone', 'photos']

  if (loading) return null;

  return (
    <Button
      title={allGranted ? 'All set' : 'Grant media access'}
      onPress={() => request()}
    />
  );
}

Built-in groups (PERMISSION_GROUPS):

| Group | Permissions | |-------|-------------| | media | camera, microphone, photos | | location | location, locationBackground | | contactsAndCalendar | contacts, calendar | | communication | sms, callPhone, contacts | | connectivity | notification, bluetooth |

You can also pass an ad-hoc array instead of a group name: usePermissionGroup(['camera', 'location']).

Props

<PermissionProvider />

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | — | App tree | | watchedPermissions | PermissionInput[] | [] | Permissions to check on mount / resume | | refreshOnForeground | boolean | true | Re-check when app becomes active |

<PermissionGate />

| Prop | Type | Default | Description | |------|------|---------|-------------| | permission | PermissionInput | — | Permission to gate on | | children | ReactNode | — | Rendered when granted / limited | | config | PermissionOptions | — | Rationale / settings options | | autoRequest | boolean | true | Request on mount if not granted | | fallback | ReactNode | null | Simple fallback UI | | renderFallback | (args) => ReactNode | — | Custom fallback with request / ensure / openSettings | | renderLoading | () => ReactNode | spinner | Loading UI |

usePermission(permission, options?) returns

| Field | Type | Description | |------|------|-------------| | status | PermissionStatus \| undefined | Current status | | granted | boolean | true if granted or limited | | loading | boolean | Check/request in progress | | error | Error \| undefined | Last error | | result | PermissionResult \| undefined | Full result object | | check() | () => Promise<PermissionResult> | Re-check | | request(options?) | () => Promise<PermissionResult> | Request | | ensure(options?) | () => Promise<PermissionResult> | Request + Settings flow | | openSettings() | () => Promise<void> | Open OS settings |

usePermissionGroup(groupNameOrList) returns

| Field | Type | Description | |------|------|-------------| | results | PermissionResult[] | One result per permission, in group order | | allGranted | boolean | true only if every permission is granted/limited | | anyGranted | boolean | true if at least one permission is granted/limited | | loading | boolean | Check/request in progress | | error | Error \| undefined | Last error | | check() | () => Promise<PermissionGroupResult> | Re-check the whole group | | request(options?) | () => Promise<PermissionGroupResult> | Request the whole group |

PermissionOptions (request / ensure / config)

| Option | Type | Description | |--------|------|-------------| | title | string | Rationale dialog title (content only — does not force the dialog to appear) | | message | string | Rationale dialog body (content only — does not force the dialog to appear) | | confirmLabel | string | Confirm button | | cancelLabel | string | Cancel button | | showRationale | boolean | Enable/disable the automatic rationale flow entirely (default: true on Android). When enabled, the dialog is only ever shown if Android's shouldShowRequestPermissionRationale returns true (i.e. after a prior denial) — never on the very first request. | | redirectToSettingsOnBlocked | boolean | Open Settings when blocked | | settingsTitle | string | Settings dialog title | | settingsMessage | string | Settings dialog body | | renderRationale | (args) => Promise<boolean> | Custom rationale UI | | renderSettingsPrompt | (args) => Promise<boolean> | Custom Settings UI |

Permission names

| Name | Enum | |------|------| | camera | PermissionType.CAMERA | | microphone | PermissionType.MICROPHONE | | photos | PermissionType.PHOTO_LIBRARY | | notification | PermissionType.NOTIFICATIONS | | contacts | PermissionType.CONTACTS | | location | PermissionType.LOCATION_FOREGROUND | | calendar | PermissionType.CALENDAR | | bluetooth | PermissionType.BLUETOOTH | | storage | PermissionType.STORAGE | | locationBackground | PermissionType.LOCATION_BACKGROUND | | sms | PermissionType.SMS | | callPhone | PermissionType.CALL_PHONE |

API

PermissionManager

| Method | Description | |--------|-------------| | check(permission) | Read current status (no prompt) | | request(permission, options?) | Request access | | requestMultiple(permissions, options?) | Batch request | | checkGroup(groupNameOrList) | Check every permission in a group (no prompt) | | requestGroup(groupNameOrList, options?) | Request every permission in a group | | ensure(permission, options?) | Request; if blocked → dialog → Settings → re-check | | openSettings() | Open OS app settings | | shouldShowRationale(permission) | Android rationale hint | | addListener(fn) / removeListener(fn) | Status change events | | getCached(permission) | In-memory snapshot |

Status values

GRANTED · DENIED · BLOCKED · LIMITED · UNAVAILABLE · NOT_DETERMINED

| Status | Meaning | Typical next step | |--------|---------|-------------------| | NOT_DETERMINED | Never asked | request() / ensure() | | DENIED | Refused, but OS may allow asking again (mainly Android after first deny) | Show rationale, then request() again | | BLOCKED | Permanently denied / cannot re-prompt from the app | openSettings() / ensure() | | GRANTED / LIMITED | Usable | Proceed | | UNAVAILABLE | Not supported, not declared in manifest/Info.plist, or native module not linked | Fix app config / rebuild |

iOS note: For Contacts, Camera, Microphone, Photos, and Notifications, a first Deny from the system dialog maps to BLOCKED (not DENIED). That matches iOS reality — the OS will not show that prompt again; only Settings can change it. Android often returns DENIED first (rationale / re-prompt possible) and upgrades to BLOCKED only after “Don’t ask again” / permanent deny.

How to run the example

# clone repo
git clone https://github.com/pawan3008/react-native-permission-manager.git
cd react-native-permission-manager

yarn install
yarn example          # install example deps

cd example
yarn android          # or: yarn ios

Example screens: Camera, Microphone, Photos, Notification, Location, Contacts, Permission Groups.

More detail: example/README.md.

FAQ

Does this support the New Architecture?
Yes — TurboModule Spec + codegen, with old bridge fallback.

What is ensure?
Requests if needed; if permanently denied (BLOCKED), shows a dialog, opens Settings, then re-checks.

Can I customize the rationale UI?
Yes — renderRationale / renderSettingsPrompt, or default Alert dialogs.

Will passing title/message to request() always pop a dialog?
No. Those strings only change the text if Android wants a rationale (after a prior deny). First request usually goes straight to the system prompt. Use showRationale: false to turn the flow off.

Do I need every permission in the manifest / Info.plist?
Only what you use. Missing Android <uses-permission>UNAVAILABLE + a warning like READ_MEDIA_IMAGES not declared in AndroidManifest.xml. Missing iOS usage string used to crash (abort_with_payload); we catch that and return UNAVAILABLE instead — still add the key if you want a real prompt.

Why does Contacts return BLOCKED right after the first Deny on iOS?
iOS won't show that dialog again from the app. BLOCKED here means "go to Settings". Android usually gives DENIED first so you can ask again.

What if the native module isn’t linked / I forgot to rebuild?
You get UNAVAILABLE and one console warning (no JS crash). Rebuild the native app (pod install on iOS).

What happens if request() is called twice at once?
Same permission (or same batch) shares one in-flight native call, so you don't get stacked system dialogs.

Architecture

See docs/ARCHITECTURE.md and docs/API.md.

Contributing

See CONTRIBUTING.md.

yarn install
yarn lint
yarn typecheck
yarn test
yarn build

License

MIT © Pawan Vishwakarma