payload-plugin-admin-alerts
v1.0.0
Published
Dashboard system alerts for the Payload CMS admin UI — author messages, target audiences, allow per-user dismissals.
Maintainers
Readme
payload-plugin-admin-alerts
Dashboard system alerts for the Payload CMS admin UI. Author messages from the admin panel, target an audience (everyone or specific users), and let users dismiss them individually.
Features
admin-alertscollection for authoring messages (title, content, type, audience, dismissable flag)- Per-user dismissals tracked inline on the alert via a
dismissedByrelationship field — no extra collection - Alert types:
info,success,warning,error - Audience targeting: broadcast to all authenticated users, or pick specific users
- "Active" / "Inactive" status for taking alerts down without deleting them
- Dashboard panel that fetches and renders alerts on the admin home page
- Per-user dismissal endpoint (
POST /admin-alerts/:id/dismiss) - Access control: only the alert author can edit or delete; any authenticated user can read
Installation
pnpm add payload-plugin-admin-alerts
# or: npm install payload-plugin-admin-alerts
# or: yarn add payload-plugin-admin-alertsPeer requirements: payload ^3.0.0, @payloadcms/ui ^3.0.0, react ^18 || ^19, react-dom ^18 || ^19.
Usage
Add the plugin to your payload.config.ts:
import { buildConfig } from 'payload'
import { payloadAdminAlerts } from 'payload-plugin-admin-alerts'
export default buildConfig({
plugins: [
payloadAdminAlerts({
// defaults shown
disabled: false,
usersCollectionSlug: 'users',
}),
],
// ...
})That's it. After Payload restarts you'll see a System → Admin Alerts entry in the admin nav and a new alerts panel on the dashboard.
Configuration
type PayloadAdminAlertsConfig = {
/**
* Disable the runtime behavior (dashboard UI + endpoints) while keeping
* the collections registered, so the database schema stays consistent.
* @default false
*/
disabled?: boolean
/**
* Auth collection slug used for alert authors and audience targeting.
* @default 'users'
*/
usersCollectionSlug?: string
}| Option | Type | Default | Notes |
| --------------------- | --------- | --------- | ---------------------------------------------------------------------------------------------------- |
| disabled | boolean | false | Skips registering the dashboard component and endpoints. Collections stay so migrations don't break. |
| usersCollectionSlug | string | 'users' | Used by the createdBy and targetUsers relationship fields, and by access control checks. |
Creating alerts from hooks
Alerts are a normal Payload collection (admin-alerts). Create them from any collection hook, job, or endpoint with the Local API — for example, notify editors when content goes live:
import type { CollectionAfterChangeHook, CollectionConfig } from 'payload'
const alertOnPublish: CollectionAfterChangeHook = async ({
doc,
operation,
previousDoc,
req,
context,
}) => {
// Only run once per publish, not on every save
if (context.skipAdminAlert) return
if (operation !== 'update') return
if (doc._status !== 'published' || previousDoc?._status === 'published') return
await req.payload.create({
collection: 'admin-alerts',
data: {
title: `Published: ${doc.title}`,
content: 'A new post is live. Review it from the dashboard if needed.',
type: 'success',
status: 'active',
audience: 'all',
dismissable: true,
},
req, // pass req so the alert stays in the same transaction
user: req.user, // createdBy is auto-set from the authenticated user
})
}
export const Posts: CollectionConfig = {
slug: 'posts',
versions: { drafts: true },
hooks: {
afterChange: [alertOnPublish],
},
fields: [
{ name: 'title', type: 'text', required: true },
// ...
],
}Target a specific user (for example, the post author) instead of broadcasting:
await req.payload.create({
collection: 'admin-alerts',
data: {
title: 'Your draft was rejected',
content: 'An editor left feedback on your submission.',
type: 'warning',
status: 'active',
audience: 'selected',
targetUsers: [authorId],
dismissable: true,
},
req,
user: req.user,
})Notes
- Pass
reqinto nestedcreatecalls so the alert participates in the same database transaction as the triggering operation. - Pass
user: req.userwhen the hook runs in an authenticated request; the plugin setscreatedByautomatically on create. - For system/cron contexts with no
req.user, useoverrideAccess: trueand setcreatedByyourself if you need an author for update/delete access. - Use
req.contextflags (for examplecontext.skipAdminAlert) if your hook chain could re-trigger itself.
Collections
admin-alerts
Authored from the admin panel. Fields:
| Field | Type | Purpose |
| -------------- | ------------------ | -------------------------------------------------------------------------------------- |
| title | text, required | Heading shown on the dashboard card |
| content | textarea, required | Body text |
| type | select | info / success / warning / error — drives the styling |
| status | select | active / inactive — only active alerts are surfaced |
| dismissable | checkbox | If true, users can dismiss the alert for themselves |
| audience | select | all or selected |
| targetUsers | relationship[] | Shown only when audience === 'selected' |
| createdBy | relationship | Auto-populated on create, read-only |
| dismissedBy | relationship[] | Users who have dismissed this alert; edit to re-show or pre-dismiss for specific users |
| alertPreview | ui field | Live preview of how the alert will render on the dashboard |
Dismissals
Dismissals live on the alert itself as the dismissedBy relationship array — no separate collection. POST /admin-alerts/:id/dismiss appends the requesting user to the field; the dashboard query filters out alerts where the current user appears in dismissedBy. To re-show an alert to a specific user, remove them from dismissedBy. To re-broadcast to everyone who'd dismissed it, clear the field.
Endpoints
The plugin registers two endpoints (mounted under your Payload API route, typically /api):
| Method | Path | Description |
| ------ | --------------------------- | ----------------------------------------------------------- |
| GET | /admin-alerts/dashboard | Returns { alerts: DashboardAlert[] } for the current user |
| POST | /admin-alerts/:id/dismiss | Records a dismissal for the current user |
Both require an authenticated request.
Access control
- Read any alert: any authenticated user
- Create an alert: any authenticated user (you become
createdBy) - Update / Delete: only the user who authored the alert
Dismissals are write-only and scoped to the requesting user.
Local development
pnpm install
pnpm dev # starts the dev/ Payload app on http://localhost:3000
pnpm test:unit # vitest unit tests
pnpm test:e2e # playwright e2e tests
pnpm check # typecheck + lint + testsThe dev/ directory contains a minimal Payload app preconfigured with the plugin.
Publishing
pnpm publishprepublishOnly cleans dist/ and rebuilds. publishConfig swaps the source-pointing exports for the compiled dist/ outputs at publish time, so consumers always get the built files.
License
MIT
Media
This plugin was tested and developed in relation to my upcoming fairytale builder app.

