New to Rust? Grab our free Rust for Beginners eBook Get it free →
Desktop Notification Like Gmail using HTML5

When an app receives an update, a message inside its page may not be visible to someone looking at another window. A desktop notification is a message the browser asks the operating system to display outside the page.
I’ll show how to request permission and display a desktop notification for an app event.
TL;DR
Use the Notifications API to show a system notification: check support, request permission from a button click, then create the notification only when permission is granted.
- Serve the page over HTTPS, or use localhost for development.
- Permission belongs to the site origin, and the visitor can deny or later revoke it.
- The Notification constructor is a desktop-page path. Mobile browsers generally need a service worker.
- A page notification does not fetch messages in the background. Push requires a separate delivery design.
What is a desktop notification in HTML5?
A desktop notification is a message the browser asks the operating system to display outside the web page. The Notifications API gives JavaScript a way to request permission and create that message, while the browser and operating system control how it looks. The visitor’s origin-level permission is required before display.
This is different from an in-page banner, which is ordinary HTML drawn inside the tab. A system notification can remain visible when the tab is not in front, but constructing one does not make the page receive new email or run a polling job in the background. The notification is an output surface, not a message transport.
Permission is scoped to the page’s origin, the combination of scheme, host and port. A visitor can accept, deny or revoke that permission, and code must handle each state.
A permission choice applies to the page’s origin, including its scheme, host and port. That boundary matters when a site uses separate subdomains or development ports, so test permissions on the exact host that serves the application.
The operating system may group, delay or suppress alerts according to its own settings. Use the app’s inbox or page state as the durable record, not the alert alone. A persistent record also serves visitors who keep system alerts disabled.
| Permission | Meaning | Application response |
|---|---|---|
| default | The visitor has not made a choice, or the status is unknown. | Explain the benefit and offer a button that asks. |
| granted | The origin may display notifications. | Create a notification for a relevant event. |
| denied | The visitor blocked notifications. | Do not prompt repeatedly. Explain where settings can be changed. |
How to show a desktop notification step by step
The flow has two separate actions: obtain consent from a visitor gesture, then display a message only after the browser reports granted permission. Keep the notification tied to an event the visitor expects.
Step 1: Serve a page over a secure origin
Serve the example over HTTPS. Browsers treat localhost as a secure development context, but opening a file or using public plain HTTP can prevent the API from working. Keep staging and production on the secure origins you intend to support.
Add a button instead of prompting as soon as the page loads. A clear action lets the visitor choose when the browser asks for permission and avoids an unexpected prompt. Do not make opt-in a condition for unrelated site features.
<button id="enable-notifications" type="button">
Enable notifications
</button>
<p id="notification-status" role="status"></p>
<script src="notifications.js" defer></script>
The status paragraph gives the page a place to explain unsupported browsers, a denial or a successful opt-in. Keep the button usable with a keyboard and do not make the notification the only way to learn about an important update.
Step 2: Check support and the saved permission
Before calling the API, check whether the browser exposes Notification. Then read Notification.permission so the page can describe the state without asking the visitor again.
| Check | Decision |
|---|---|
| Notification is missing from window | Show a page-level explanation and keep the feature unavailable. |
| permission is granted | Allow the visitor to test or enable a relevant notification. |
| permission is denied | Do not call requestPermission again. Explain the browser settings route. |
| permission is default | Wait for the visitor to click the enable button. |
The permission value is not a promise that a notification will be visible: system focus modes, browser policy and operating-system settings can still affect presentation. It tells the page whether it may attempt to create one.
Keep the permission check near the action that needs it. If the visitor has already granted access, the feature can continue without displaying another browser prompt. If access is denied, a page-level explanation is more useful than repeatedly asking.
Step 3: Request permission from the button click
Call Notification.requestPermission() directly from the click handler and await its Promise. The result is granted, denied or default, so continue only for granted.
const button = document.querySelector("#enable-notifications");
const status = document.querySelector("#notification-status");
button.addEventListener("click", async () => {
if (!("Notification" in window)) {
status.textContent = "Notifications are not supported here.";
return;
}
if (Notification.permission === "denied") {
status.textContent = "Allow notifications in your browser settings.";
return;
}
const permission = Notification.permission === "granted"
? "granted"
: await Notification.requestPermission();
if (permission !== "granted") {
status.textContent = "Notifications were not enabled.";
return;
}
status.textContent = "Notifications are enabled.";
});
Calling the permission method in response to a click matches browser guidance and makes the purpose of the prompt clear. If permission is already granted, the code skips another request and proceeds with the existing choice.
The method returns a Promise, so await pauses this click handler until the visitor responds. The application should handle the resolved value instead of assuming that the visitor chose Allow, and should not rely on the deprecated callback form for new code.
Step 4: Create the notification for an actual event
After consent, create a Notification with a short title and an options object. The body gives context, the icon can identify the application, and a click handler can take the visitor to the matching page. Keep sensitive detail out of text that might appear on a shared screen.
function showNewMessage(message) {
if (!("Notification" in window) || Notification.permission !== "granted") {
return;
}
const notification = new Notification("New message", {
body: message.preview,
icon: "/images/notification-icon.png",
tag: `message-${message.id}`
});
notification.addEventListener("click", () => {
window.focus();
window.location.href = `/messages/${encodeURIComponent(message.id)}`;
notification.close();
});
}
Call showNewMessage only when the application has received a new message through its own foreground data flow. The tag identifies related notifications so a newer pending notification can replace one with the same tag instead of adding an unbounded stack.
Replace the example paths and message fields with values from your app. Do not put private message content in a notification if someone else might see the device screen, and keep an in-page inbox as the reliable way to find the full message.
A tag is useful when several updates refer to one changing item, such as the latest state of a task. Choose a stable identifier for that item. Giving unrelated events the same tag can cause one alert to replace another, while unique tags can leave the visitor with a long row of separate notices.
Step 5: Verify the result in a browser
Serve the page from its secure deployment origin. Click the button, choose Allow, and trigger a test event while the browser window is not focused. Check the browser’s site-permission controls if no prompt appears.
- Confirm that the page reports support and the permission state you expect.
- After allowing permission, trigger one test message and check the operating system notification area.
- Click the notification and confirm that it opens the matching page in the application.
- Reset the origin permission in browser settings and repeat the denied and default paths.
Notification appearance and dismissal are controlled partly by the browser and operating system, so the same code can produce different layouts. Verify the behavior on the browser and device combinations your application supports.
The operating system also controls where and when an alert appears. If a notification is delivered but not noticed, check those settings and focus mode as well as the browser permission.
What can stop a desktop notification from appearing?
Failures commonly come from the origin, a permission state or choosing an API path the device does not support. Diagnose those inputs before changing the notification options.
| Symptom | Likely cause | Next check |
|---|---|---|
| No prompt appears | Permission was already decided, the request was not triggered by a click, or the browser suppresses prompts. | Read Notification.permission and the site’s permission settings. |
| Request or constructor fails | The page is not in a secure context, or the API is unavailable. | Use HTTPS or localhost and test the support check. |
| Constructor throws on a phone | The non-persistent Notification constructor is not supported in nearly all mobile browsers. | Use a registered service worker and showNotification for the mobile route. |
| No message arrives when the page is closed | Creating a notification does not provide background message delivery. | Design a push subscription and server delivery path separately. |
| Prompt was denied | The user or browser blocked the origin. | Respect the choice and explain how to change it in browser settings. |
The Notification constructor creates a non-persistent page notification. Mobile web notifications generally use ServiceWorkerRegistration.showNotification() instead, and that route involves service-worker registration and different event handling.
Push is another layer: a service worker can receive a push event when the page is not open, but the application needs a subscription and a server that sends messages. A timer in an open tab is not a substitute for that design, and a notification call alone does not supply it.
Use close() when a notification has become irrelevant, such as when a visitor has already read the corresponding item. Avoid closing every alert after an arbitrary short delay because that can remove it from the system tray before the visitor acts.
Common errors with desktop notifications
When a notification does not appear, check the browser permission and secure origin before changing the message options. The device can also suppress an allowed notification through its own focus or notification settings.
| Error or symptom | Check | Next step |
|---|---|---|
| Notification is undefined | The browser does not expose the Notifications API in this context. | Keep the in-page status message and provide another way to see updates. |
| Permission stays default or the prompt does not open | Confirm the request follows a visitor action and check the origin’s saved site permission. | Ask only after the visitor clicks, and respect a denied choice. |
| Notification constructor throws | Check the secure context and whether the device supports page-created notifications. | Use HTTPS or localhost, and use a service worker for supported mobile flows. |
| Permission is granted but no alert is visible | Check browser and operating-system notification settings, including focus mode. | Keep the event in the app’s inbox so the alert is not the only record. |
| No new message arrives after the tab closes | The page notification API does not provide background delivery. | Implement push delivery separately with a service worker and server. |
Conclusion
A desktop notification starts with a clear permission choice, not with a constructor call on page load. Check support, request consent from a visitor action, and create an alert only when permission is granted and the message is useful.
For API details, read the MDN Notifications API guide, the requestPermission() reference, and the WHATWG Notifications Standard. Use the service-worker route when the application needs persistent or mobile notifications, and plan push delivery separately.
Frequently asked questions
These answers cover the common implementation questions about permission, HTTPS and delivery.
Do browser desktop notifications require HTTPS?
The Notifications API requires a secure context in supporting browsers. Use HTTPS for a deployed site and localhost for development.
Can I request notification permission when the page loads?
Request permission after a clear user action, such as clicking an Enable notifications button. Browsers may block requests made on page load.
Why does the Notification constructor fail on mobile?
The Notification constructor is unsupported in nearly all mobile browsers. Register a service worker and use its showNotification() method for the mobile path.
Does a desktop notification work when the page is closed?
Creating a notification does not deliver new data to a closed page. Background delivery requires a separate push and service-worker implementation.




