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

@elghaied/payload-plugin-notifications

v1.0.0

Published

In-dashboard notifications for the Payload admin, with a live SSE bell.

Readme

@elghaied/payload-plugin-notifications

In-dashboard notifications for the Payload admin, with a live bell that updates without a page refresh.

  • 🔔 A NotificationBell in the admin header — unread count + dropdown feed with compact relative timestamps ("now", "40m", "2d"); unread rows highlighted, read rows dimmed; click a row to mark it read and go where it points.
  • 📋 A custom notifications list view — Payload's default table is replaced with a clean, paginated feed (same row, whole-row click marks read + navigates). Reached via "See all".
  • Live via Server-Sent Events (best-effort), with graceful degradation when SSE can't connect.
  • 🗄️ Every notification is a real DB row (the source of truth) — adapter-agnostic (Mongo or Postgres), opaque ids.
  • 🔒 Notification links are scheme-validated before navigation (no javascript:/data: XSS).
  • 🧩 Additive and default-off; optional multi-tenant scoping.

Screenshots

| Bell dropdown | Custom list view | |---|---| | Notification bell dropdown open over the admin, showing recent notifications with relative timestamps | Custom notifications list view replacing Payload's default table |

Unread rows carry an accent bar and bolded text; read rows are dimmed. Times are compact and relative (now, 8m, 3h, 2d, 1w), with the full timestamp on hover.

How it works

Two decoupled layers:

  1. Source of truth — a notifications collection row. Created via pushNotification(). A user sees their notifications whether or not a tab was open when they were created.
  2. Live channel — an afterChange hook on the collection emits each new row over SSE to that recipient's open admin tabs (in-memory connection registry). This is the only live-push path, and it's a side effect of the DB write — never a separate path.

Designed for a long-running Node process (self-hosted), where SSE connections hold. On serverless or behind a buffering proxy the bell still shows accurate counts on mount and on dropdown-open — it just won't pop instantly.

Install

pnpm add @elghaied/payload-plugin-notifications

Usage

// payload.config.ts
import { payloadPluginNotifications } from '@elghaied/payload-plugin-notifications'

export default buildConfig({
  plugins: [
    payloadPluginNotifications(),
  ],
})

That adds the notifications collection (hidden from the nav) and the live bell in the admin header.

Sending a notification

pushNotification is a thin wrapper over payload.create (with overrideAccess), callable from any hook, endpoint, or job:

import { pushNotification } from '@elghaied/payload-plugin-notifications'

await pushNotification(payload, {
  recipient: user.id,
  message: 'Invoice #1042 was paid',
  link: '/admin/collections/invoices/1042',
  type: 'success', // 'info' | 'warning' | 'success'
})

// Inside a hook, pass `req` so the create joins the same transaction:
await pushNotification(req.payload, { recipient, message: 'Updated', req })

REST create is denied — notifications are system-generated. Each user can only read, mark-read, and dismiss their own notifications.

Configuration

payloadPluginNotifications({
  disabled,          // boolean   — default false. Installed but inert; DB schema stays stable.
  notificationsSlug, // string    — default 'notifications'
  usersSlug,         // string    — default 'users'. The auth collection notifications target.
  tenants: {         // object    — OMIT for single-tenant (recipient-only scoping).
    tenantsSlug,     //   string  — default 'tenants'
    tenantFieldName, //   string  — default 'tenant'
  },
  hideFromNav,       // boolean   — default true. The bell is the UI surface.
})

Multi-tenancy

When the tenants block is set, the plugin adds a required tenant relationship field and ANDs a tenant filter into read access. It interoperates with @payloadcms/plugin-multi-tenant but doesn't depend on it.

The default filter assumes one tenant per user (user[tenantFieldName]). The official multi-tenant plugin models users with a tenants array — for that model you'll want a custom tenant filter (a tenantFilter config hook is the planned extension point).

Notifications collection

| field | type | notes | |---|---|---| | recipient | relationship → users | required | | tenant | relationship → tenants | only when multi-tenant is enabled (required) | | message | text | required | | link | text | e.g. /admin/collections/invoices/123 | | type | select | info | warning | success | | read | checkbox | default false |

Extension points (not built in v1)

  • Background jobs / digestspushNotification() is the single seam where payload.jobs.queue() would swap in for async or scheduled/digest notifications.
  • Multi-replica live push — the NotificationRegistry interface (in-memory today) accepts a distributed implementation (Postgres LISTEN/NOTIFY or Mongo change streams). No Redis.

License

MIT