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.
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.
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.
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:
- Never on page load, and never on the first denied session. The denial is fresh; the answer is still no.
- 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.
- 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 intry/catchand fall back to pollingNotification.permissiononvisibilitychange, 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.
Related
- Re-Permission and Recovery Flows — the wider recovery strategy this guidance sits inside
- Recovering Users Who Blocked Push Permission — nudge placement and how to measure recovery rate
- Detecting Denied Push Permission Without Prompting — the full silent detection surface, including permissionState()
- iOS Web Push Requires Add to Home Screen — why iOS users can look blocked without ever being eligible
- Building Notification Preference Segments — where to record the fallback channel a blocked user chose
Back to Re-Permission and Recovery Flows