Strapi Notifier
A first-class notification system for Strapi v5 admin panels. Adds a live bell icon to the sidebar, a full notification inbox, a runtime Settings panel, and a simple API to send notifications from anywhere in your Strapi application.
strapi-plugin-notifier
A first-class notification system for Strapi v5 admin panels. Adds a live bell icon to the sidebar, a full notification inbox, a runtime Settings panel, and a simple API to send notifications from anywhere in your Strapi application.
Features
- Live bell icon — badge count updated by polling the server (interval configurable)
- Notification inbox — filter by type, mark as read, dismiss, load more, clear all
- Targeting — broadcast to all admins, or target by user ID or role code
- Batch sending — send multiple notifications in one call, settings fetched once
- Merge — collapse repeated notifications into one item with a count badge (fully configurable)
- User opt-out — each admin user can mute individual notification types or opt out entirely
- Declarative rules — trigger notifications from lifecycle hooks, eventHub events, or cron schedules — no code required beyond
config/plugins.ts - Configurable — poll interval, page size, retention policy, UI accent colours, merge rules, and cleanup schedule
- Settings panel — runtime configuration via Settings → Notifier (persisted in Strapi plugin store)
- Retention cron — configurable cleanup schedule (default: 3 AM daily), respects maxDays and maxPerUser limits
- Strapi v5 only
Installation
npm install strapi-plugin-notifier
# or
yarn add strapi-plugin-notifierEnable the plugin in config/plugins.ts:
export default {
notifier: {
enabled: true,
config: {
// all fields are optional — see Configuration below
},
},
};Configuration
All configuration is optional. Built-in defaults apply unless overridden.
// config/plugins.ts
export default {
notifier: {
enabled: true,
config: {
retention: {
maxDays: 90, // delete notifications older than N days
maxPerUser: 500, // cap notifications stored per user
cleanupCron: '0 3 * * *', // cron schedule for cleanup job (default: 3 AM daily)
},
delivery: {
pollIntervalMs: 30_000, // how often the Bell polls (ms)
pageSize: 20, // notifications per page in inbox
},
merge: {
enabled: false, // enable notification merging
windowMinutes: 60, // look back this many minutes for a merge candidate
keyFields: ['title', 'type'], // fields that must match to merge
countBadge: true, // show "×N" badge on merged items in the inbox
rewriteMessage: false, // rewrite the message to "N× title" when merging
},
ui: {
theme: {
accent: {
info: '#4945ff',
success: '#5cb85c',
warning: '#f0ad4e',
error: '#ee5e52',
},
},
},
},
},
};Settings can also be updated at runtime from the Strapi admin panel under Settings → Notifier. Runtime settings take precedence over config/plugins.ts.
Sending notifications
Use the notifier service from anywhere in your Strapi code (services, controllers, lifecycles, webhooks):
const notifier = strapi.plugin('notifier').service('notifier');
// Broadcast to all admin users
notifier.broadcast({ title: 'Maintenance scheduled', type: 'warning' });
// Send to a specific role
notifier.toRole('strapi-editor', {
title: 'New content submitted',
message: 'An article is waiting for review.',
url: '/content-manager/collection-types/api::article.article',
});
// Send to a specific admin user (by user ID)
notifier.toUser(42, {
title: 'Your export is ready',
type: 'success',
url: '/uploads/export-2024.csv',
});
// Generic send with full control
notifier.send({
title: 'Hello',
message: 'World',
type: 'info',
url: 'https://example.com',
to: { role: 'strapi-super-admin' },
});Batch sending
Send multiple notifications in one call. Settings (including merge config) are fetched once and applied to all items — more efficient than looping over send().
await notifier.sendBatch([
{ title: 'New job application', type: 'info', to: { role: 'strapi-editor' } },
{ title: 'Export ready', type: 'success', to: { userId: 42 } },
{ title: 'Low disk space', type: 'warning' }, // broadcast
]);Opt-out and merge rules are applied per item within the batch.
Notification options
| Field | Type | Required | Default |
|---|---|---|---|
title | string | Yes | — |
message | string | No | — |
type | 'info' \| 'success' \| 'warning' \| 'error' | No | 'info' |
url | string | No | — |
to | { userId?: number; role?: string } | No | broadcast |
Notification merging
When merge.enabled is true, sending a notification whose key fields match an existing notification (created within windowMinutes) updates that notification instead of creating a new one.
Example — a form that can be submitted many times:
// config/plugins.ts
merge: { enabled: true, windowMinutes: 60, keyFields: ['title', 'type'], countBadge: true }
// In your lifecycle / service:
notifier.toRole('strapi-editor', {
title: 'New job application',
type: 'info',
});After five submissions the inbox shows a single item: New job application ×5.
Merge key fields (title, type, url) — any combination. Two notifications are considered the same if all selected fields match AND the recipient matches AND the most recent one was created within the window.
Display options:
| Option | Default | Effect |
|---|---|---|
countBadge | true | Shows a coloured "×N" pill next to the title |
rewriteMessage | false | Replaces the message with "N× <title>" |
Both can be toggled from the Settings → Notifier → Merge panel at runtime.
User opt-out
Each admin user can control which notifications they receive. Opt-out is applied at query time so it covers broadcast and role-targeted notifications too.
Via the API
GET /notifier/preferences/me
PUT /notifier/preferences/meGet current preferences:
GET /notifier/preferences/me
→ { "data": { "userId": 5, "globalOptOut": false, "mutedTypes": ["info"] } }Mute info and success notifications:
PUT /notifier/preferences/me
Content-Type: application/json
{ "mutedTypes": ["info", "success"] }Opt out entirely:
PUT /notifier/preferences/me
Content-Type: application/json
{ "globalOptOut": true }Re-enable:
PUT /notifier/preferences/me
Content-Type: application/json
{ "globalOptOut": false, "mutedTypes": [] }Preference fields
| Field | Type | Default | Description |
|---|---|---|---|
globalOptOut | boolean | false | Suppress all notifications for this user |
mutedTypes | NotificationType[] | [] | Suppress only these notification types |
Declarative rules
Instead of calling the notifier service in code, you can declare rules in config/plugins.ts. The plugin wires up the listeners at bootstrap — notifications fire automatically with no further code changes needed.
Three trigger types are supported: lifecycle, event, and cron.
on: 'lifecycle' — content-type create/update/delete
Fires when Strapi's DB lifecycle executes for a given content-type and action.
// config/plugins.ts
notifier: {
enabled: true,
config: {
rules: [
{
on: 'lifecycle',
model: 'api::article.article',
action: 'afterCreate', // afterCreate | afterUpdate | afterDelete | afterPublish | afterUnpublish
notification: {
title: 'New article: {{entry.title}}',
message: 'Created by {{entry.createdBy.firstname}}',
type: 'info',
to: { role: 'strapi-editor' },
},
},
],
},
},entry in templates is the DB record produced by the lifecycle action (result for after-hooks, params.data for before-hooks).
on: 'event' — strapi.eventHub subscription
Fires on any named event published to Strapi's internal event hub (lifecycle events, media events, plugin events, or your own custom events emitted with strapi.eventHub.emit()).
{
on: 'event',
event: 'media.upload', // any strapi.eventHub event name
filter: { uid: 'api::article.article' }, // optional shallow key=value filter on payload
notification: {
title: 'File uploaded: {{media.name}}',
type: 'info',
to: 'broadcast',
},
},Custom events from your own services:
// In your service
strapi.eventHub.emit('order.placed', { order });
// In config/plugins.ts
{
on: 'event',
event: 'order.placed',
notification: {
title: 'New order: #{{order.id}}',
message: '{{order.customerName}} — €{{order.total}}',
type: 'success',
to: { role: 'strapi-super-admin' },
},
},on: 'cron' — time-based notifications
Fires on a cron schedule. Standard 5-field cron expressions.
{
on: 'cron',
schedule: '0 9 * * 1', // Every Monday at 9 AM
notification: {
title: 'Weekly reminder: review pending content',
type: 'warning',
to: { role: 'strapi-editor' },
},
},Notification templates
Every field in notification accepts either a static value or a function for dynamic content.
String interpolation — use {{path.to.field}} dot-notation to resolve values from the event context:
notification: {
title: 'New order from {{entry.customer.name}}',
message: 'Total: {{entry.total}} — shipped to {{entry.address.city}}',
}Function templates — for logic that can't be expressed as a template string:
notification: {
title: (ctx) => `${ctx.entry.priority === 'high' ? '🔴' : ''} Order #${ctx.entry.id}`,
type: (ctx) => ctx.entry.priority === 'high' ? 'error' : 'info',
}Targeting in rules
The to field supports the same options as the programmatic API, plus a userIdFrom path resolver:
| Value | Behaviour |
|---|---|
'broadcast' (or omitted) | Send to all admin users |
{ role: 'strapi-editor' } | Send to all users with this role code |
{ userId: 42 } | Send to a specific admin user |
{ userIdFrom: 'entry.createdBy.id' } | Resolve user ID via dot-path into the event context — useful for "notify the author" patterns |
// Notify the author when their content is rejected
{
on: 'lifecycle',
model: 'api::article.article',
action: 'afterUpdate',
when: (ctx) => ctx.entry.status === 'rejected',
notification: {
title: 'Your article was rejected',
to: { userIdFrom: 'entry.createdBy.id' },
},
},when guard
Add a when function to conditionally skip the notification. Return false to suppress it.
{
on: 'lifecycle',
model: 'api::article.article',
action: 'afterUpdate',
when: (ctx) => ctx.entry.publishedAt != null, // only fire when published
notification: {
title: 'Article published: {{entry.title}}',
type: 'success',
to: 'broadcast',
},
},when is not available on cron rules (there is no event context to filter on).
API routes
All routes require admin::isAuthenticatedAdmin. The plugin mounts under /notifier/.
| Method | Path | Description |
|---|---|---|
| GET | /notifier/notifications | List notifications (paginated) |
| PUT | /notifier/notifications/read-all | Mark all as read |
| DELETE | /notifier/notifications | Clear all notifications |
| PUT | /notifier/notifications/:id/read | Mark one as read |
| DELETE | /notifier/notifications/:id | Clear one notification |
| GET | /notifier/config | Fetch UI config (safe for frontend) |
| GET | /notifier/settings | Get full settings |
| PUT | /notifier/settings | Update settings |
| DELETE | /notifier/settings | Reset settings to defaults |
| GET | /notifier/preferences/me | Get current user's notification prefs |
| PUT | /notifier/preferences/me | Update current user's notification prefs |
Query parameters for GET /notifier/notifications:
| Param | Default | Description |
|---|---|---|
page | 1 | Page number |
pageSize | 20 | Results per page |
Permissions
Two permissions are registered under the Notifier plugin section in Settings → Roles:
plugin::notifier.settings.read— view the settings panelplugin::notifier.settings.update— save or reset settings
Content types
| UID | Collection | Description |
|---|---|---|
plugin::notifier.notification | notifier_notifications | Notification records |
plugin::notifier.notification-preference | notifier_preferences | Per-user opt-out preferences |
Both are hidden from Content Manager and Content-Type Builder by default.
License
MIT
Install now
npm install strapi-plugin-notifier
Create your own plugin
Check out the available plugin resources that will help you to develop your plugin or provider and get it listed on the marketplace.