JavaScript PushManager permissionState() Method

Beginner
5 min read
Updated: Nov 2026
5 examples
Method

Definition

PushManager.permissionState() returns a Promise that resolves to "prompt", "denied", or "granted" (MDN). Use it to see if push is allowed before calling subscribe().

Pass the required arguments shown below. Check return values and browser support before you rely on the result.

Syntax

Call pushManager.permissionState. Start with this line.

JavaScript
pushManager.permissionState(options?)
CallWhat it does
pushManager.permissionState(options?)Primary signature.
pushManager.permissionState(...)Call with the arguments your case needs.

Built into the browser. Call permissionState(...) on a PushManager — no import required.

Library declarations
JavaScript
pushManager.permissionState(options?)

Parameters

ParameterDescription
optionsOptional. userVisibleOnly, applicationServerKey
Secure contextRequired. HTTPS or localhost

Return Value

Return ValueDescription
*Resolves to status string

JavaScript Examples

Example 1 — Basic Check

The standard call after serviceWorker.ready.

JavaScript
const registration = await navigator.serviceWorker.ready;
const status = await registration.pushManager.permissionState({
  userVisibleOnly: true,
});

console.log(status); // "prompt" | "denied" | "granted"
Try It Yourself

How It Works

  1. Wait for ready. You need a registered service worker.
  2. Pass userVisibleOnly. Chrome requires visible notifications for web push.
  3. Await the Promise. Resolves to a status string.
  4. No prompt yet. This check does not ask the user — subscribe() may.

Example 2 — UI Branch

Map each status to a clear button label.

JavaScript
function labelFor(status) {
  if (status === "granted") return "Push Enabled";
  if (status === "denied") return "Push Blocked";
  return "Enable Push"; // prompt
}

const registration = await navigator.serviceWorker.ready;
const status = await registration.pushManager.permissionState({
  userVisibleOnly: true,
});

console.log(labelFor(status));
Try It Yourself

How It Works

  1. Read the status. Same three strings every time.
  2. Map to copy. Users understand “Blocked” better than denied.
  3. Keep subscribe separate. Only enable the action on prompt or granted.
  4. Denied needs settings help. The page cannot unblock push itself.

Example 3 — Feature Detect First

Guard before calling — older environments may lack PushManager.

JavaScript
const hasAPI =
  typeof PushManager !== "undefined" &&
  typeof PushManager.prototype.permissionState === "function";

console.log(hasAPI ? "permissionState available" : "not supported");

if (hasAPI && "serviceWorker" in navigator) {
  const registration = await navigator.serviceWorker.ready;
  const status = await registration.pushManager.permissionState({
    userVisibleOnly: true,
  });
  console.log(status);
}
Try It Yourself

How It Works

  1. Check the prototype. Avoids calling a missing method.
  2. Also need a worker. PushManager comes from a registration.
  3. Then call. Same options pattern as example 1.
  4. Tip. Insecure HTTP pages often fail before this check matters.

Example 4 — With applicationServerKey

Pass the same VAPID public key you will use in subscribe().

JavaScript
function urlBase64ToUint8Array(base64String) {
  const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
  const raw = atob(base64);
  return Uint8Array.from([...raw].map((c) => c.charCodeAt(0)));
}

// Use YOUR application's VAPID public key in production
const key = urlBase64ToUint8Array(
  "BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkrURzf19mqjzGDll3yGobi0"
);

const registration = await navigator.serviceWorker.ready;
const status = await registration.pushManager.permissionState({
  userVisibleOnly: true,
  applicationServerKey: key,
});

console.log(status);
Try It Yourself

How It Works

  1. Decode the key. VAPID keys are URL-safe Base64 → Uint8Array.
  2. Pass both options. Match your future subscribe() call.
  3. Still a string result. Permission state, not a subscription.
  4. Use your own key. The sample key is for demos only.

Example 5 — Replace hasPermission()

Modern code uses permissionState; map legacy "default" to "prompt".

JavaScript
async function getPushStatus(pm) {
  if (typeof pm.permissionState === "function") {
    return pm.permissionState({ userVisibleOnly: true });
  }
  if (typeof pm.hasPermission === "function") {
    const legacy = await pm.hasPermission();
    return legacy === "default" ? "prompt" : legacy;
  }
  return typeof Notification !== "undefined"
    ? Notification.permission
    : "unsupported";
}

console.log("prefer permissionState()");
Try It Yourself

How It Works

  1. Prefer modern. Call permissionState when present.
  2. Normalize legacy. Map "default" → "prompt".
  3. Optional sync hint. Notification.permission is a fallback string.
  4. See also. hasPermission() (deprecated).

Browser Support

PushManager.permissionState() is marked Baseline Widely available on MDN (since March 2023). Secure context required. DOM / Service Worker API — not a Node.js built-in.

✓ Baseline · Widely available

permissionState()

Check push permission as prompt, denied, or granted across modern browsers with a service worker.

96% Core tag support
Chrome 42+
Full support
Edge 17+
Full support
Firefox 44+
Full support
Safari 16+
Full support
Opera 29+
Full support
HTML element 100% supported

Bottom line: Safe for modern browsers. Call after serviceWorker.ready on HTTPS/localhost, then subscribe() from a user gesture when status is prompt or granted.

Frequently Asked Questions

A Promise that resolves to "prompt", "denied", or "granted" — the push permission state for the given options (MDN).
No. MDN marks PushManager.permissionState() as Baseline Widely available (since March 2023). It is not Deprecated, Experimental, or Non-standard.
An optional object with userVisibleOnly (boolean) and applicationServerKey (VAPID public key). Match the options you will use with subscribe().
granted means push is allowed; denied means blocked; prompt means the user has not decided yet (a dialog may appear when you subscribe).
permissionState() is the modern standard. It uses "prompt" instead of legacy "default" and accepts subscribe options like userVisibleOnly.
On registration.pushManager after navigator.serviceWorker.ready, in a secure context (HTTPS or localhost). MDN also notes it is available in Web Workers.

Next: getSubscription()

See if a PushSubscription already exists before asking again.

getSubscription() →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

8 people found this page helpful