npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

notification-kit

v2.1.3

Published

One API for push, in-app, and local notifications across Web, iOS, and Android.

Readme

notification-kit

One API for push, in-app, and local notifications across Web, iOS, and Android.

npm version downloads license types unpacked size node

Docs · npm · GitHub · Changelog · AI Guide · Support

[!IMPORTANT] notification-kit has zero runtime dependencies. Provider SDKs (firebase, react-onesignal) and Capacitor plugins are not installed for you — you add only the ones you use. In-app notifications work with nothing else installed. See Requirements.

notification-kit gives React and Capacitor apps a single notification API instead of three. Push notifications (Firebase Cloud Messaging or OneSignal), local scheduled notifications, and in-app toasts all share one call surface, one permission model, and one event stream, so the same code runs on Web, iOS, and Android. It is provider-less by design: no React context, no wrapper component — initialise once, then call from anywhere, including code outside the React tree.

| | | |---|---| | Version | 2.1.3 | | License | MIT | | Node | ≥ 20 (developed and built on 24) | | Platforms | Web · iOS · Android (iOS/Android via Capacitor) | | Install size | ≈ 93 KB packed · ≈ 430 KB unpacked | | Types | Bundled — .d.ts for both ESM and CJS | | Runtime dependencies | None | | Status | Stable, actively maintained |

🧭 Table of Contents #

💡 Why notification-kit #

A cross-platform app usually ends up with three separate notification stacks: an FCM or OneSignal web SDK, a Capacitor plugin pair for native, and a toast library for in-app messages. Each has its own permission call, its own event names, and its own idea of what a notification looks like. Feature code ends up branching on platform.

notification-kit collapses that into one module:

  • One call surface. notifications.schedule(...) behaves the same on Web, iOS, and Android.
  • One permission model. requestPermission() maps to the right native or browser prompt.
  • Swappable provider. Firebase and OneSignal sit behind the same interface, so changing provider is a change to init(), not to your feature code.
  • No provider component. Nothing to mount, so notifications work in modals, route loaders, service layers, and dynamically injected components alike.
  • Pay only for what you use. Provider SDKs load through dynamic import(), so an app that only shows in-app toasts never pulls Firebase into its bundle.

✨ Features #

| Feature | Detail | |---|---| | Push notifications | Firebase Cloud Messaging or OneSignal, with token retrieval, refresh, and topic subscription | | Local notifications | Scheduling by absolute time, relative delay, or repeat interval | | In-app notifications | Toast-style messages rendered by the library — no extra dependency, no provider | | Unified permissions | One requestPermission() across the browser Notification API and native prompts | | Android channels | Create, list, and delete notification channels with importance and visibility | | Event stream | Typed on() / off() for received, opened, action, token, and permission events | | Platform detection | Reports the running platform and its real notification capabilities | | Zero runtime dependencies | Nothing is installed on your behalf; optional SDKs load dynamically | | Dual ESM + CJS | import and require both work, each with its own type declarations | | TypeScript-first | Written in TypeScript; every public type is exported | | Setup CLI | notification-kit-setup scaffolds provider config and service workers |

📱 Platform Support #

| Platform | Push | Local | In-app | Notes | |---|:---:|:---:|:---:|---| | Web | ✅ | ✅ | ✅ | Push needs a service worker and HTTPS. Safari needs 16.4+, and on iOS the site must be installed to the Home Screen. | | iOS (Capacitor) | ✅ | ✅ | ✅ | Push requires a physical device, an APNs key, and the Push Notifications capability. Simulators cannot receive push. | | Android (Capacitor) | ✅ | ✅ | ✅ | Android 13+ requires the runtime POST_NOTIFICATIONS permission. Channels are required from Android 8. |

In-app notifications need only a DOM, so they work in any browser context — including a plain React app with no Capacitor installed.

📋 Requirements #

Node ≥ 20 to build. At runtime the library itself needs nothing — every integration below is an optional peer dependency you install only if you use it.

| Peer | Minimum | Needed for | |---|---|---| | react, react-dom | 19.2.6 | the notification-kit/react hooks | | @capacitor/core | 8.3.4 | any native (iOS/Android) behaviour | | @capacitor/push-notifications | 8.1.1 | native push | | @capacitor/local-notifications | 8.2.0 | native local notifications | | @capacitor/preferences | 8.0.1 | native persistent storage | | firebase | 12.13.0 | the Firebase Cloud Messaging provider | | react-onesignal | 3.5.3 | the OneSignal provider |

A missing optional peer is not a crash: the feature that needs it throws a message naming the package to install, and everything else keeps working.

📦 Installation #

yarn add notification-kit

Then add only what your app actually uses:

# Native (iOS/Android) support
yarn add @capacitor/core @capacitor/push-notifications @capacitor/local-notifications @capacitor/preferences
npx cap sync

# Firebase Cloud Messaging provider
yarn add firebase

# OneSignal provider
yarn add react-onesignal

# React hooks
yarn add react react-dom

Optionally scaffold provider config and service workers:

npx notification-kit-setup

🚀 Quick Start #

1. Initialise once, as early as your app boots:

import { NotificationKit } from 'notification-kit';

await NotificationKit.init({
  provider: 'firebase', // or 'onesignal'
  config: {
    // Firebase: the six web-app values plus vapidKey for web push.
    // OneSignal: just { appId }.
    apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
    authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
    projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
    storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
    appId: import.meta.env.VITE_FIREBASE_APP_ID,
    vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY,
  },
});

2. Use it anywhere — no provider component, no context:

import { notifications } from 'notification-kit';

await notifications.requestPermission();

await notifications.schedule({
  id: 'standup-reminder',
  title: 'Standup',
  body: 'Starting in 5 minutes',
  in: { minutes: 5 },
});

await notifications.success('Saved', 'Your changes are stored.');

3. Or use the React hooks:

import { useNotifications, useInAppNotification } from 'notification-kit/react';

function SaveButton() {
  const { isPermissionGranted, requestPermission } = useNotifications();
  const notify = useInAppNotification();

  const save = async () => {
    if (!isPermissionGranted) await requestPermission();
    await notify.success('Saved');
  };

  return <button onClick={save}>Save</button>;
}

🛠️ Usage #

Push notifications

import { notifications } from 'notification-kit';

const token = await notifications.getToken(); // register this with your server
await notifications.subscribe('news');

// onPush fires in the foreground; onPushOpened fires when the user taps.
// Both return their own unsubscribe function.
const stopListening = notifications.onPush((notification) => { /* … */ });

notifications.onPushOpened((notification) => {
  if (notification.data?.screen) router.push(notification.data.screen);
});

stopListening();

Local notifications

id, title, and body are required. Choose one timing option — in (relative), at (absolute), or every (repeating, optionally pinned to a time with on).

import { notifications } from 'notification-kit';

await notifications.schedule({
  id: 'meeting-5m',
  title: 'Reminder',
  body: 'Meeting in 5 minutes',
  in: { minutes: 5 },
});

await notifications.schedule({
  id: 'standup',
  title: 'Daily standup',
  body: 'Team meeting starting',
  at: new Date('2026-08-20T09:00:00'),
});

await notifications.schedule({
  id: 'morning-routine',
  title: 'Morning routine',
  body: 'Start your day right',
  every: 'day',
  on: { hour: 7, minute: 30 },
});

// Cancel by the id you supplied
await notifications.cancel('meeting-5m');

For a weekly notification use every: 'week' with on: { weekday, hour, minute }, where weekday is 1 (Monday) through 7 — one schedule per day you want. Full options: local notifications guide.

In-app notifications

These need no provider and no permission — they render into the DOM.

import { notifications, dismissInAppNotification } from 'notification-kit';

// Four shorthands: success, error, warning, info
await notifications.success('Saved successfully');
await notifications.error('Something went wrong');

// Or the full form, which resolves to the notification's id
const id = await notifications.showInApp({
  title: 'New message',
  message: 'John sent you a message',
  type: 'info',
  duration: 5000,
  position: 'top-right',
  action: { label: 'View', onClick: () => router.push('/messages') },
});

await dismissInAppNotification(id);

⚙️ Configuration #

Everything is configured in the same init() call shown in Quick Start. Beyond provider and config, two optional blocks tune in-app notifications — inApp controls where and how long one appears, styles controls how it looks. Colours live under styles.colors, not under inApp.

await NotificationKit.init({
  provider: 'firebase',
  config: { /* provider credentials */ },

  inApp: {
    position: 'top-right', // 7 positions, from 'top-left' to 'center'
    duration: 4000,
    maxStack: 3,
    zIndex: 9999,
  },

  styles: {
    theme: 'auto', // 'light' | 'dark' | 'auto'
    colors: { success: '#10B981', error: '#EF4444', warning: '#F59E0B', info: '#3B82F6' },
    fontFamily: 'system-ui',
    borderRadius: '8px',
  },

  debug: false,
});

NotificationConfig also accepts serviceWorkerPath, storage, environment, and features — see configuration.

Per-platform setup — Info.plist keys, google-services.json, the web service worker — is covered in the docs: iOS · Android · Web.

🔧 API Reference #

Full reference: notification-kit-docs.aoneahsan.com/reference/api-overview.

notifications — the everyday surface

// Permissions and tokens
requestPermission(): Promise<boolean>
checkPermission(): Promise<PermissionStatus>
isPermissionGranted(): Promise<boolean>
getToken(): Promise<string>
deleteToken(): Promise<void>

// Topics and push listeners — each listener returns its own unsubscribe
subscribe(topic: string): Promise<void>
unsubscribe(topic: string): Promise<void>
onPush(cb): () => void
onPushOpened(cb): () => void

// Local notifications
schedule(options: ScheduleOptions & LocalNotificationPayload): Promise<void>
cancel(id: string | number): Promise<void>
cancelAll(): Promise<void>
getPending(): Promise<Notification[]>

// In-app notifications — resolve to the new notification's id
showInApp(options: InAppOptions): Promise<string>
success | error | warning | info (title: string, message?: string): Promise<string>

// Events
on(event, callback): () => void
off(event, callback?): void

Also available for managing already-delivered notifications: getDelivered(), removeDelivered(id), and removeAllDelivered().

NotificationKit — the instance surface

Channel management, teardown, and capability checks live on the singleton rather than on notifications:

NotificationKit.init(config): Promise<void>          // static
const kit = NotificationKit.getInstance();

kit.createChannel(channel) · kit.deleteChannel(id) · kit.listChannels()
kit.isSupported() · kit.getPlatform() · kit.getCapabilities()
kit.isInitialized() · kit.destroy()

React hooks — notification-kit/react

useNotifications() returns the permission state (isPermissionGranted is a boolean, not a function), token helpers, topic subscription, scheduling, channel management, and an showInApp group. useInAppNotification() returns success / error / warning / info / show / dismiss / dismissAll plus hasActive, activeCount, and the onShow / onDismiss subscriptions. useInAppNotificationSimple, useInAppNotificationQueue (sequential toast display), and useInAppNotificationPersistence are also exported. Signatures: React hooks reference.

🧩 Types #

Written in TypeScript; every public type is exported from the package root and re-exported from notification-kit/react. Both the ESM and CJS builds ship their own declarations, so import and require consumers get identical types.

import {
  type NotificationConfig,
  type ScheduleOptions,
  type LocalNotificationPayload,
  type InAppOptions,
  type NotificationChannel,
  type PermissionStatus,
  type Platform,
} from 'notification-kit';

const reminder: ScheduleOptions & LocalNotificationPayload = {
  id: 'meeting',
  title: 'Meeting',
  body: 'Starting in 10 minutes',
  in: { minutes: 10 },
  data: { meetingId: '123' },
};

Worth knowing when reading the types:

  • schedule() takes ScheduleOptions & LocalNotificationPayload, so id is required — that id is the handle you later pass to cancel().
  • ChannelImportance is numeric (15), not a string.
  • Weekday is 17, Monday through Sunday.

Full type reference: config types.

💻 Command Line #

npx notification-kit-setup

An interactive scaffolder. It detects your framework and whether Capacitor is present, asks which provider you want, then writes the provider config and the matching service worker into your project. It needs an interactive terminal and is not designed for CI.

🧪 Examples #

A runnable React + Capacitor app lives in the repository at examples/react-capacitor-example. It covers initialisation, the React hooks, all three notification kinds, permissions, and token handling.

More worked examples: docs quick start.

🎛️ Advanced Features #

Android notification channels

Required from Android 8. Importance is numeric — 5 is highest.

import { NotificationKit } from 'notification-kit';

const kit = NotificationKit.getInstance();

await kit.createChannel({
  id: 'important',
  name: 'Important notifications',
  description: 'Critical app notifications',
  importance: 5,
  sound: 'notification.wav',
  vibration: true,
  lights: true,
  lightColor: '#FF0000',
});

Then pass channelId: 'important' when scheduling. Details: channels guide.

Grouping and presentation

Notifications sharing a group collapse together on Android; mark one of them groupSummary: true as the header. largeIcon, color, autoCancel, ongoing, and badge are also accepted per notification.

await notifications.schedule({
  id: 'msg-summary',
  title: 'New messages',
  body: 'You have 3 new messages',
  group: 'messages',
  groupSummary: true,
  in: { seconds: 5 },
});

Reacting to actions

notifications.on('notificationActionPerformed', (event) => {
  switch (event.actionId) {
    case 'complete':
      return markTaskComplete(event.notification.id);
    case 'snooze':
      return snoozeNotification(event.notification.id);
  }
});

The full event list — received, opened, action, token, permission, subscribe — is in the events guide.

Platform capabilities

platform.detect() reports the running platform; await platform.getCapabilities() reports what notification features it actually supports, so you can hide UI a platform cannot honour.

🚑 Recovery & Troubleshooting #

| Symptom | Likely cause | Fix | |---|---|---| | Cannot find module 'firebase' at runtime | the optional peer is not installed | yarn add firebase (or react-onesignal for OneSignal) | | A call throws "not initialized" | it ran before init() resolved | await NotificationKit.init(...) at boot, before first use | | Nothing arrives on iOS | running on a simulator, or the capability is missing | test on a physical device, enable Push Notifications in Xcode, configure the APNs key | | Push silently absent on Android 13+ | POST_NOTIFICATIONS was never granted | call requestPermission() from a user action, then re-check | | Local notifications never fire on Android | no channel, or a channel with importance below 3 | create a channel and pass its channelId | | Web push does nothing | no service worker, or the page is not HTTPS | serve over HTTPS and register the worker notification-kit-setup generates | | schedule() fails to typecheck | id was omitted | id is required — it is also the handle cancel() needs | | In-app toast does not appear | init() never ran, or app CSS overrides the container | check isInitialized(), then check z-index on the injected container |

Longer guide: help/troubleshooting.

🚧 Limitations #

Stated plainly, so nothing surprises you after you adopt it:

  • Two providers only — Firebase Cloud Messaging and OneSignal. There is no public adapter interface for registering a third provider from outside the package.
  • Sending is not included. The library receives and schedules. Sending push to your users is a server job; OneSignal's REST key is deliberately never used from client code.
  • No offline queue. Local notifications are handed to the OS immediately; there is no store-and-forward buffer for actions taken while offline.
  • No built-in localisation. Notification text is whatever you pass in, so it localises with your app — the library ships no translation layer.
  • In-app notifications carry no ARIA wiring. They render plain DOM without live-region announcements, so if you need screen-reader support today you must announce the message yourself.
  • Native behaviour needs a real device. Push cannot be verified on an iOS simulator, and exact-time Android scheduling is subject to Doze and per-OEM battery optimisation.
  • Web push on Safari requires 16.4+, and on iOS the site must be installed to the Home Screen.

❓ FAQ #

Do I need Capacitor? No. Without Capacitor you get web push, web local notifications, and in-app notifications. Capacitor only adds the native iOS and Android paths.

Do I have to wrap my app in a provider component? No. init() configures a singleton, so any module can import notifications and call it — including code that never renders.

Can I switch from Firebase to OneSignal later? Yes. Change provider and config in init(). The call surface your features use does not change.

Does it really have zero dependencies? The published package declares no dependencies. Provider SDKs and Capacitor plugins are optional peers you install yourself, loaded through dynamic import() only when the feature runs.

Why does schedule() not return an id? Because you supply it. id is required in the options and is the same handle you pass to cancel().

Does it work with Next.js or plain React? Yes, for the web paths. Guard init() so it only runs in the browser, since notification APIs do not exist during server rendering.

Is it tree-shakeable? The ESM build is marked side-effect-free, so a bundler can drop exports you never import. Provider code sits behind dynamic imports, so an app using only in-app notifications does not bundle Firebase or OneSignal.

📚 Documentation #

The full documentation site is notification-kit-docs.aoneahsan.com.

| Question | Page | |---|---| | What is this and how does it fit together? | Introduction | | How do I install it? | Installation | | How do I get something working fast? | Quick start | | What can I configure? | Configuration | | How do push notifications work end to end? | Push notifications | | How do I schedule local notifications? | Local notifications | | How do I show in-app toasts? | In-app notifications | | What do the React hooks give me? | React hooks | | How do Android channels work? | Channels | | How do I handle permissions properly? | Permissions | | Which events can I listen to? | Events | | What native setup does each platform need? | iOS · Android · Web | | How do I set up my provider? | Firebase · OneSignal | | What is the exact API? | API overview · NotificationKit · notifications · React hooks | | What do the types mean? | Config types | | Something is broken | Troubleshooting · FAQ | | I am an AI coding agent | AI Integration Guide |

🔄 Changelog #

Every released version is documented in CHANGELOG.md, newest first, following Keep a Changelog and semantic versioning. It also renders on the docs site at /changelog.

🤝 Contributing #

Contributions are welcome. main is protected, so changes land through a pull request with a review: fork the repository, branch, and open a PR. Read CONTRIBUTING.md first — it covers the development setup, the quality gates a PR must pass, and how to request collaborator access.

💬 Support #

Found a bug or want a feature? Open an issue at github.com/aoneahsan/notification-kit/issues — naming your platform, provider, and package version makes it far faster to diagnose.

If this package saves you time, you can support its development.

📄 License #

MIT © Ahsan Mahmood — see LICENSE.

👤 Author #

Ahsan Mahmood

🔗 Links #

| Resource | URL | |---|---| | Documentation | https://notification-kit-docs.aoneahsan.com | | npm | https://www.npmjs.com/package/notification-kit | | Source | https://github.com/aoneahsan/notification-kit | | Issues | https://github.com/aoneahsan/notification-kit/issues | | Changelog | https://github.com/aoneahsan/notification-kit/blob/main/CHANGELOG.md | | Contributing | https://github.com/aoneahsan/notification-kit/blob/main/CONTRIBUTING.md | | AI Integration Guide | https://github.com/aoneahsan/notification-kit/blob/main/AI-INTEGRATION-GUIDE.md | | Example app | https://github.com/aoneahsan/notification-kit/tree/main/examples/react-capacitor-example | | Docs source | https://github.com/aoneahsan/notification-kit-docs | | Support the project | https://aoneahsan.com/payment?project-id=notification-kit&project-identifier=notification-kit |

🏷️ Keywords #

capacitor · react · notifications · push-notifications · local-notifications · in-app-notifications · firebase · onesignal · typescript · ios · android · web