Skip to content
usefy

usePermission

Browser & device

Permissions API status with live updates — SSR-safe, typed, and race-safe.

Install

$ npm install @usefy/use-permission

Quick start

usePermission.tsx
import { usePermission } from "@usefy/use-permission";

function CameraStatus() {
  const { state, status } = usePermission({ name: "camera" });

  // Branch on `status` (deterministic "idle" on the first render, both on the
  // server and client) so the UI is hydration-safe.
  if (status === "idle" || status === "pending") return <span>Checking…</span>;
  if (status === "unsupported") return <span>Permissions API unavailable</span>;
  if (status === "error") return <span>Could not read camera permission</span>;

  return <span>Camera: {state}</span>; // 'granted' | 'denied' | 'prompt'
}

API reference

usePermission(descriptor)

function usePermission(descriptor: UsePermissionDescriptor): UsePermissionReturn;

Parameters

ParamTypeDescription
descriptorUsePermissionDescriptorThe permission to query, e.g. { name: 'camera' }, { name: 'geolocation' }, { name: 'push', userVisibleOnly: true }, { name: 'midi', sysex: true }. name accepts the standard PermissionName values (with autocomplete) plus any browser-specific string. Pass an inline literal freely — the hook keys re-queries on the descriptor's serialized contents, not its object identity.

Returns — UsePermissionReturn

FieldTypeDescription
statePermissionState | nullThe raw permission state ('granted' | 'denied' | 'prompt'), or null until the first query resolves and whenever the API is unsupported or the query errored.
statusUsePermissionStatusCoarse lifecycle: 'idle' | 'pending' | 'granted' | 'denied' | 'prompt' | 'unsupported' | 'error'. The three permission values mirror state, so you can branch on either.
isSupportedbooleanWhether navigator.permissions.query is available. Starts false (on the server and the first client render, for hydration safety) and becomes true after mount in a supporting browser. Branch your first render on status ('idle'), not isSupported.
errorError | nullThe error thrown by navigator.permissions.query(), if the query rejected (e.g. an unknown permission name in a browser that throws).

Helpers

  • isPermissionsSupported(): boolean — capability check used internally; exported for feature-detection.
  • serializeDescriptor(descriptor): string — the stable-key function the hook uses to decide when to re-query (exported for advanced use; not part of the umbrella surface).

Notes on behavior

  • Descriptor identity. usePermission({ name: 'camera' }) receives a fresh object literal every render. Rather than require callers to useMemo it, the hook derives a stable string key from the descriptor's own fields (sorted, JSON-serialized) and keys its effect on that. It re-queries only when the meaningful contents change, e.g. name 'camera''microphone' or userVisibleOnly truefalse.
  • Unsupported. If navigator.permissions is missing (SSR or an unsupporting browser), status is 'unsupported', isSupported is false, and state is null.
  • Errors. Some browsers reject query() for permission names they don't recognize (e.g. camera in Firefox) instead of returning 'denied'. That surfaces as status: 'error' with the thrown error.
  • Race safety. If the component unmounts while a query is in flight, the stale resolution/rejection is ignored — no state update after unmount — and the change listener is always removed on cleanup.

Go deeper

This page is the quick reference. For every example, prop, and edge case, read the full README — or open Storybook to change props live.

More in browser & device