Guiding Users to Reset Blocked Notification Permission

Notification.permission === 'denied' is not a state your code can leave. There is no API to re-request, no flag to clear, no reload that resets it. The only actor who can move that value back to 'default' is the user, inside browser settings, using UI you cannot see or drive.

Quick answer

Detect the denial silently by reading Notification.permission — the read costs nothing and shows no UI. Then, at a moment when notifications are demonstrably useful, show short text instructions naming three landmarks: the site-controls icon beside the address bar, the Notifications row inside the panel it opens, and the reset control at the bottom. Never ship a screenshot per browser; the menu strings change every few releases and the screenshots rot faster than the copy does.

Why “denied” is a one-way door

Notification.requestPermission() from a denied state resolves immediately with 'denied'. It does not throw, it does not queue, and — importantly for anyone instrumenting a funnel — it does not draw a dialog. The promise settles in single-digit milliseconds, which is the cheapest way to tell a real dismissal apart from a state that was already decided.

The decision is stored per origin, not per tab or per session, and Chrome hardens it further: three ignored prompts on an origin and subsequent requests are routed into quiet UI, which behaves like a soft denial without ever being reported as one.

Notification permission states and the one-way transition into denied Notification.permission starts at default and moves to granted when the user allows or denied when the user blocks or repeatedly dismisses. Denied is terminal for every API; only the user, in browser settings, can return the origin to default. Permission states only the user, in browser settings, can return this to "default" Notification.permission "default" — never asked repeated dismissals count as a block Allow Block "granted" subscribe() permitted "denied" terminal for every API Browser settings user resets by hand requestPermission() resolves "denied" at once, with no dialog
Every arrow out of "denied" leaves your code and enters the browser's own settings UI.

Detecting the block without prompting

Read the state, do not probe it. Notification.permission is a synchronous property with no side effects, and navigator.permissions.query() gives you a live object you can subscribe to, so the page reacts the moment the user resets the setting in another window.

export function pushPermissionState() {
  if (!('Notification' in window) || !('PushManager' in window)) return 'unsupported';
  if (!window.isSecureContext) return 'insecure-context';
  return Notification.permission; // 'default' | 'granted' | 'denied'
}

// Live updates: fires when the user changes the setting in browser UI,
// including in another tab or another window, with no reload.
export async function watchPushPermission(onChange) {
  if (!navigator.permissions) return () => {};
  const status = await navigator.permissions.query({ name: 'notifications' });
  const handler = () => onChange(status.state);
  status.addEventListener('change', handler);
  onChange(status.state);
  return () => status.removeEventListener('change', handler);
}

The change event is what turns a reset from a dead end into a completed flow: the user follows your instructions in the browser’s own panel, the state flips to 'prompt', and your page can swap the instructions for a working subscribe button without the user having to find their way back. Wire it before you show any guidance. The wider detection surface, including pushManager.permissionState(), is covered in detecting denied push permission without prompting.

Where the control lives, per browser

Every desktop browser puts the same control behind an icon immediately left of the URL. What differs is the icon, the panel’s name, and whether the reset is a per-permission dropdown or a single button.

Per-browser paths to reset a blocked notification permission Matrix listing, for Chrome desktop, Chrome on Android, Firefox, Safari on macOS and Edge, where the notification permission setting lives and which control the user changes to clear the block. Reset paths by browser Browser Where the setting lives What the user changes Chrome desktop Tune-slider icon left of the URL, then Site settings Notifications dropdown to Allow, or the Reset permissions button Chrome Android Tap the icon in the address bar, then Permissions Toggle Notifications on; it may sit one level down under Site settings Firefox desktop Padlock icon, then the blocked permission row in the panel Clear the Blocked entry, or remove the site under Settings, Permissions Safari macOS Safari menu, then Settings, Websites, Notifications Set the site's row to Allow, or delete the row entirely Edge desktop Padlock icon, then Permissions for this site Notifications dropdown to Allow, or the Reset permissions button Menu strings drift between releases. Describe the landmark, not the label.
Five reset paths. Four of them start at an icon immediately left of the address bar.

Safari on iOS is the exception worth calling out separately. Push there only exists for web apps added to the Home Screen, and the permission is managed by iOS rather than by Safari’s site settings — once the web app has asked, it appears under Settings, Notifications, alongside native apps. If it never reached that point, removing the Home Screen icon and re-adding it is the reliable reset. The prerequisites are in iOS web push requires Add to Home Screen, and getting them wrong produces users who look blocked but were never eligible.

The three landmarks

Strip every browser’s panel down and the same three things are there: an entry point next to the URL, a row named Notifications showing its current value, and a reset control that clears everything for the origin. Write your instructions against those three, and the copy survives a redesign.

The site-settings surface, annotated A browser window mock with the address bar, a site-settings panel listing Notifications as Blocked plus Location and Camera set to Ask, and a Reset permissions button, with three numbered callouts naming the site controls icon, the Notifications row and the reset control. What the user is looking for https://news.example.com 1 news.example.com Notifications Blocked 2 Location Ask Camera Ask Reset permissions 3 Wording varies; the control does not. 1 Site controls icon Left of the address bar: a tune slider in Chrome, a padlock in Firefox and Edge. 2 Notifications row Reads Blocked. Switching it to Ask or Allow clears the denial for this origin only. 3 Reset control Clears every permission for the site, so the next request is allowed to prompt again. Three landmarks: the icon, the Notifications row, the reset. Only the labels drift between browsers.
The same three landmarks appear in every desktop browser's site-settings panel.

Writing instructions that do not need a screenshot

Screenshot-per-browser guides fail for three reasons: they are wrong within two release cycles, they cannot cover the combinatorial spread of browser and operating system and theme, and they are invisible to screen readers unless you write the alt text you were trying to avoid writing. Text instructions built from landmarks avoid all three.

Four rules make the copy work:

  • Name the position, then the appearance. “The icon just left of the web address — it looks like a small slider or a padlock” beats “click the tune icon”, because the reader finds it by location even when the glyph has changed.
  • Give the row a value to look for. “Find the row labelled Notifications; it currently says Blocked” turns scanning into matching. If the row says something else, the user learns immediately that they are in the wrong panel.
  • Offer the reset as the fallback, never as step one. Resetting clears location, camera and microphone too. Lead with the Notifications dropdown and mention reset as the option when the dropdown is not there.
  • Keep it to three steps and no more than about 25 words per step. Anything longer will not be read while the user is holding a phone.

Detect the family, pick the phrasing, and fall back to something generic rather than to something wrong:

const RESET_COPY = {
  chrome: [
    'Tap the icon just left of the web address.',
    'Open Permissions, then find Notifications.',
    'Switch Notifications on, or choose Reset permissions.'
  ],
  firefox: [
    'Click the padlock just left of the web address.',
    'Find the Notifications row marked Blocked.',
    'Clear the block with the X beside it.'
  ],
  safari: [
    'Open the Safari menu, then Settings.',
    'Choose Websites, then Notifications.',
    'Set this site to Allow.'
  ],
  generic: [
    'Open your browser settings for this site.',
    'Find the Notifications permission.',
    'Change it from Blocked to Allow, then reload.'
  ]
};

function resetSteps(ua = navigator.userAgent) {
  if (/Firefox\//.test(ua)) return RESET_COPY.firefox;
  if (/Edg\//.test(ua)) return RESET_COPY.chrome;      // same panel shape
  if (/Chrome\//.test(ua)) return RESET_COPY.chrome;
  if (/Safari\//.test(ua)) return RESET_COPY.safari;
  return RESET_COPY.generic;
}

Render the steps as an ordered list inside a disclosure, not as a modal. A user who is blocked is a user who already said no once; a dialog they cannot dismiss without reading is the wrong second impression.

<details class="permission-help">
  <summary>Notifications are blocked for this site — how to turn them back on</summary>
  <ol id="reset-steps" aria-live="polite"></ol>
  <p>Once you have changed the setting, this page updates on its own.</p>
</details>

The aria-live="polite" region and the permissions.query() listener together mean the flow completes without a reload — which matters, because a reload sends the user back to the top of a page they were reading. The same accessibility considerations that apply to the in-page opt-out surface apply here; see designing accessible push notification opt-out flows.

Choosing the moment

Instructions shown at the wrong time are noise. The trigger should be an action that only makes sense with notifications attached — tapping “Notify me when this is back in stock”, enabling a price alert, subscribing to a thread. At that point the user has stated the intent, and the instructions read as help rather than as a second attempt at the ask they already refused.

Three rules keep it from becoming nagging:

  1. Never on page load, and never on the first denied session. The denial is fresh; the answer is still no.
  2. Cap the surface. Once per feature attempt, at most once per calendar week, and never more than three lifetime impressions. Persist the counter server-side where you can, since a blocked user is often the same user clearing storage.
  3. Make it dismissible and remember the dismissal. A closed disclosure that reopens on the next page view is the behaviour that produced the block in the first place.

The measurement side — recovery rate, time-to-reset, and how to attribute a recovered subscription — is covered in recovering users who blocked push permission.

What to offer instead

Most blocked users will never reset the setting, and designing as if they will is how a recovery flow turns into a nag. Put a working alternative next to the instructions, always:

  • Email or SMS for the same trigger. A back-in-stock alert does not care which transport delivers it. Capture the address inline rather than sending the user to a preferences page.
  • In-app inbox. A badge on your own UI, read on the next visit. No permission required, and it works on every iOS browser regardless of Home Screen status.
  • Calendar or RSS for scheduled content. Underused, zero-permission, and preferred by exactly the sort of user who blocks notifications.

Store the chosen fallback as a preference so the same person is not asked twice, using the model in building notification preference segments. A blocked user who accepted email is a converted user, not a lost one.

Gotchas and edge cases

  • permissions.query({ name: 'notifications' }) throws in some older WebKit builds. Wrap it in try/catch and fall back to polling Notification.permission on visibilitychange, which catches the return-from-settings case on mobile.
  • Resetting permissions does not restore the old subscription. The endpoint was never created, or was invalidated when the block landed. After a reset you must call pushManager.subscribe() again and persist a fresh endpoint.
  • A granted permission after a reset can still coexist with a stale server row. If the user previously subscribed, blocked, then reset and re-subscribed, you now hold two endpoints for one person; deduplicate on the user identifier, not on the endpoint string.
  • Enterprise policy can pin the setting. Managed Chrome deployments can lock notifications off for all origins, and the panel shows the control greyed out. Nothing in your flow will work, so cap impressions and let the fallback channel carry the feature.
  • Incognito and private windows report 'denied' for reasons that have nothing to do with the user. Detect the private context where you can and skip the guidance entirely rather than teaching someone to fix a setting that resets when the window closes.

FAQ

Can JavaScript reset or re-request a denied notification permission?

No. There is no API for it in any browser, by design — a page that could clear its own denial would make the permission meaningless. Notification.requestPermission() from a denied state resolves immediately with ‘denied’ and draws no dialog. Clearing site data, unregistering the service worker and re-registering it, or serving from a different path all leave the setting untouched, because it is keyed to the origin and stored by the browser outside anything the page controls.

How do I know when the user has actually reset the setting?

Subscribe to the PermissionStatus returned by navigator.permissions.query({ name: ‘notifications’ }) and listen for its change event. It fires when the user alters the setting in browser UI, including from another window, with no reload required. Use the event to swap your instructions for a subscribe button. Where navigator.permissions is unavailable, re-read Notification.permission on the visibilitychange event, which covers the common case of a mobile user returning from the settings screen.

Should I show reset instructions to everyone who is blocked?

No. Show them only at a moment where notifications are the obvious mechanism for something the user just asked for, cap them at roughly three lifetime impressions, and always pair them with an alternative channel. Most blocked users will not change the setting, and a recovery flow that assumes otherwise becomes indistinguishable from the nagging that caused the block. Recovery rates in the low single digits of the blocked population are normal.

Back to Re-Permission and Recovery Flows