Thin wrapper around the Web Notifications API via the $notify magic. Handles unsupported browsers and permission states without throwing.
pnpm add @ailuracode/alpine-notify @ailuracode/alpine-core @ailuracode/alpine-permissions alpinejsimport Alpine from "alpinejs";
import notify from "@ailuracode/alpine-notify";
Alpine.plugin(notify);
Alpine.start();Copy the bundled service worker to your site root (or another same-origin path):
cp node_modules/@ailuracode/alpine-notify/dist/notify-sw.js public/notify-sw.jsThe plugin registers /notify-sw.js automatically. Use a custom path when needed:
Alpine.plugin(
notify({
serviceWorkerUrl: "/assets/notify-sw.js",
}),
);If your application already owns a $notify magic or another toolkit plugin registers on that name, rename the integration surface without touching the controller:
Alpine.plugin(notifyPlugin({ magicKey: "alerts" })); // → $alertsThe exposed constant DEFAULT_NOTIFY_MAGIC_KEY keeps the rename discoverable from TypeScript.
| Member | Type | Description |
|---|---|---|
isSupported |
boolean (getter) |
true when notifications can be shown in this environment |
requiresHomeScreenInstall |
boolean (getter) |
true on iOS/iPadOS Safari tabs that need a Home Screen install |
permission |
NotificationPermission (getter) |
granted, denied, or default |
requestPermission() |
Promise<NotificationPermission> |
Prompts the user when permission is default |
send(title, options?) |
Notification | null |
Creates a desktop notification synchronously |
sendAsync(title, options?) |
Promise<Notification | null> |
Preferred on mobile; uses a service worker when needed |
sendIfPermitted(title, options?) |
Notification | null |
Same as send — explicit intent in templates |
sendIfPermittedAsync(title, options?) |
Promise<Notification | null> |
Same as sendAsync |
close(notification) |
void |
Closes a notification safely |
Use getters without parentheses in templates: $notify.isSupported, $notify.permission.
All methods except requestPermission() are synchronous. Nothing throws when notifications are unavailable.
$notify.send("Hello");$notify.send("Order completed", {
body: "Your payment was successful.",
icon: "/logo.png",
});<button
x-show="$notify.isSupported && $notify.permission === 'default'"
@click="await $notify.requestPermission()"
>
Enable notifications
</button>await $notify.requestPermission();
await $notify.sendAsync("You are subscribed");$notify.sendIfPermitted("Background job finished");<div
x-data="{ note: null }"
@job-complete.window="note = $notify.sendIfPermitted('Done')"
>
<button x-show="note" @click="$notify.close(note); note = null">
Dismiss
</button>
</div><div x-show="!$notify.isSupported && !$notify.requiresHomeScreenInstall">
Notifications are not supported in this browser.
</div>
<div x-show="$notify.requiresHomeScreenInstall">
Add this site to your Home Screen on iPhone or iPad to enable notifications.
</div>
<div x-show="$notify.isSupported && $notify.permission === 'denied'">
Notifications are blocked. Enable them in browser settings.
</div>- Unsupported browsers —
isSupportedisfalse,permissionreturnsdenied,send/sendIfPermittedreturnnull. - iOS/iPadOS Safari tabs —
requiresHomeScreenInstallistrue; notifications only work after the user adds the site to the Home Screen and opens it from there. - Android and mobile Chrome —
new Notification()is not available; the plugin usesServiceWorkerRegistration.showNotification()via the bundlednotify-sw.js. - Denied permission —
Notificationis never constructed; methods returnnullordeniedwithout throwing. - Default permission —
sendreturnsnulluntil the user grants access viarequestPermission(). - Granted permission — use
sendAsync()on mobile andsend()on desktop.
The plugin does not render UI, manage toast stacks, or persist preferences. Use your own components for in-app messaging and permission UX.
| Environment | Notes |
|---|---|
| Chrome, Edge, Opera (desktop) | Supported in secure contexts via new Notification() |
| Firefox (desktop) | Supported in secure contexts |
| Safari (macOS 16.4+) | Supported in secure contexts |
| Chrome (Android) | Requires the bundled service worker and sendAsync() |
| Safari (iOS / iPadOS) | Home Screen web app only; regular Safari tabs cannot receive notifications |
| HTTP (non-localhost) | Blocked — requires HTTPS |
| Web Workers / Service Workers | This plugin targets window / Alpine templates in the main document |
Always check isSupported, requiresHomeScreenInstall, and permission before showing permission prompts or assuming notifications will appear.
Register with @ailuracode/alpine-permissions for a normalized snapshot across capabilities:
import { permissionsPlugin } from "@ailuracode/alpine-permissions";
import { createNotificationPermissionAdapter } from "@ailuracode/alpine-notify";
Alpine.plugin(
permissionsPlugin({
adapters: [createNotificationPermissionAdapter()],
})
);Registry key: notifications. See permissions.md.
/// <reference types="@types/alpinejs" />
/// <reference types="@ailuracode/alpine-notify" />Or import the plugin module:
import notify from "@ailuracode/alpine-notify";Individual helpers are also exported for non-Alpine use:
import {
createNotifyMagic,
isNotifySupported,
sendNotification,
} from "@ailuracode/alpine-notify";- Magic, not store — notifications are one-off actions, not shared reactive state.
- Fail silent — returning
nullkeeps Alpine expressions and event handlers simple. - No UI coupling — framework-agnostic; pair with your own toast or banner components for in-page feedback.
MIT