caiquebispo / notification-bell
Pacote elegante e responsivo para gerenciamento de notificações em Laravel com Livewire
Requires
- php: ^8.1
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- livewire/livewire: ^3.5.0|^4.0.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.5|^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
An elegant, framework-agnostic Laravel package for managing notifications with Livewire, featuring per-user preferences, full dark mode support, zero-build self-contained assets, and a robust delivery pipeline.
Highlights
Zero build, zero publish
The package ships its own self-contained CSS (prefixed nb-), served directly from vendor/ through a package route. No Tailwind, no Bootstrap, no npm run build, no vendor:publish needed — install via Composer, drop the component in your layout, done. It looks right in any project: Tailwind, Bootstrap, or plain CSS.
End-user control
Each user manages their own experience through a preferences panel inside the bell:
- Toggle popup toasts and sounds (with volume control)
- Mute notification categories individually
- "Do not disturb" snooze (1h / 4h / 8h / 24h)
- Daily quiet hours (e.g. 22:00–08:00, midnight-crossing supported)
- Pin important notifications to the top
- Archive instead of delete
- Undo accidental deletions (soft delete + undo toast)
- Clear all notifications in the current tab, with an elegant inline confirmation (no native browser dialogs anywhere)
Robust delivery pipeline
- Deduplication — repeated
dedup_keywithin a time window is dropped - Rate limiting — per-user per-minute cap against notification floods
- Scheduling & expiry —
scheduled_at/expires_atrespected automatically - Smart grouping — N notifications sharing a
group_keycollapse into one expandable item - Laravel events —
NotificationCreated,NotificationRead,NotificationArchived,NotificationDeleted - Broadcasting — real-time via Echo/Reverb/Pusher with automatic polling fallback
- REST API — optional JSON endpoints for mobile apps and SPAs
- Soft deletes — nothing is lost by accident; cleanup purges for real
- Test suite — PHPUnit + Testbench covering models, dispatcher, preferences, Livewire component and API
Installation
composer require caiquebispo/notification-bell php artisan migrate
Add the trait to your User model:
use CaiqueBispo\NotificationBell\Traits\HasNotifications; class User extends Authenticatable { use HasNotifications; }
Include the component in your layout (Livewire 3 bundles Alpine.js — nothing else to install):
@auth <livewire:notification-bell /> @endauth @livewireScripts
That's it. The component injects its own stylesheet automatically. If you prefer loading it in the <head>:
<head> @notificationBellStyles </head>
Optional publishing
Publishing is optional — only for deep customization:
php artisan vendor:publish --tag="notification-bell-config" # config/notifications.php php artisan vendor:publish --tag="notification-bell-views" # Blade views php artisan vendor:publish --tag="notification-bell-lang" # translations php artisan vendor:publish --tag="notification-bell-migrations" # migrations
Theming
Customize the look through config — applied via CSS variables, no build step:
'theme' => [ 'mode' => 'auto', // auto | system | dark | light (see below) 'primary' => '#8b5cf6', // any CSS color, or names like 'violet' 'badge_background' => '#ef4444', 'badge_text' => '#ffffff', 'badge_style' => 'count', // count | dot | pulse 'badge_position' => 'top-right', // top-right | top-left | bottom-right | bottom-left 'radius' => '0.75rem', 'dropdown_width' => '22rem', 'toast_position' => 'bottom-right',// + top-left, top-center, bottom-center... 'bell_icon' => null, // Blade view with your own SVG 'item_view' => null, // Blade view to render each list item ],
Type colors come from the types section, so custom types get proper colors automatically:
'types' => [ 'info' => ['color' => 'blue', 'icon' => 'info-circle'], 'success' => ['color' => 'green', 'icon' => 'check-circle'], 'warning' => ['color' => 'yellow', 'icon' => 'exclamation-triangle'], 'error' => ['color' => 'red', 'icon' => 'x-circle'], 'billing' => ['color' => '#8b5cf6', 'icon' => 'bell'], // custom type ],
Dark mode (theme.mode):
auto(default) — follows the host page's explicit markers only:.dark,[data-theme="dark"], or[data-bs-theme="dark"](Bootstrap). A light site stays light even for users whose OS is in dark mode.system— additionally follows the OSprefers-color-schemewhen no marker is present.dark/light— force one look regardless of the host.
Per-instance overrides
<livewire:notification-bell :limit="5" :polling="false" />
User preferences
Enabled by default. Users open the gear icon inside the bell to control toasts, sound, volume, muted categories, snooze and quiet hours. Persisted in the notification_preferences table.
'preferences' => [ 'enabled' => true, 'allow_sound_control' => true, 'allow_toast_control' => true, 'allow_category_control' => true, 'allow_snooze' => true, 'allow_quiet_hours' => true, 'snooze_options' => ['1h' => 60, '4h' => 240, '8h' => 480, '24h' => 1440], ],
Programmatic access:
$prefs = $user->bellPreferences(); $prefs->muteCategory('marketing'); $prefs->snoozeFor(120); $prefs->isSnoozed(); // true
Categories
Group notifications above the type level. Users can mute categories they don't care about:
'categories' => [ 'orders' => ['label' => 'Pedidos'], 'system' => ['label' => 'Sistema'], 'marketing' => ['label' => 'Novidades'], ],
NotificationHelper::info($userId, 'Sale!', '50% off today', null, null, [ 'category' => 'marketing', ]);
A notification sent to a user who muted its category is silently skipped.
Creating notifications
use CaiqueBispo\NotificationBell\Helpers\NotificationHelper; // Simple NotificationHelper::info($userId, 'Title', 'Message'); NotificationHelper::success($userId, 'Order confirmed', 'Order #123 confirmed!', ['order_id' => 123], route('orders.show', 123)); // Multiple users NotificationHelper::create([1, 2, 3], 'Maintenance', 'Scheduled for tonight', 'warning'); // Full options NotificationHelper::create($userId, 'New comment', 'Someone replied', 'info', null, '/posts/1', [ 'category' => 'social', 'group_key' => 'post-1-comments', // groups with similar notifications 'dedup_key' => 'comment-42', // duplicate within window is dropped 'image_url' => '/avatars/maria.png', 'scheduled_at' => now()->addHour(), // deliver later 'expires_at' => now()->addDays(3), // disappears after 'queue' => false, // bypass the queue, create synchronously ]); // Via the User trait auth()->user()->success($userId, 'Done!', 'Operation completed.');
Action buttons
NotificationHelper::info($userId, 'Approval needed', 'A document awaits review', [ 'actions' => [ ['label' => 'Approve', 'url' => '/docs/1/approve', 'style' => 'primary'], ['label' => 'Reject', 'url' => '/docs/1/reject', 'style' => 'danger'], ], ]);
Delivery guards
'deduplication' => [ 'enabled' => true, 'window' => 300, // seconds ], 'rate_limit' => [ 'enabled' => true, 'max_per_minute' => 30, // per user ],
Events
React in your app without coupling to the package:
use CaiqueBispo\NotificationBell\Events\NotificationCreated; use CaiqueBispo\NotificationBell\Events\NotificationRead; use CaiqueBispo\NotificationBell\Events\NotificationArchived; use CaiqueBispo\NotificationBell\Events\NotificationDeleted; Event::listen(NotificationCreated::class, function ($event) { // e.g. mirror to e-mail or Slack $event->notification; });
Broadcasting (real-time)
'broadcasting' => [ 'enabled' => true, 'channel' => 'notifications.{user_id}', 'event' => 'NotificationCreated', 'private' => true, 'fallback_to_polling' => true, ],
With Laravel Echo (Reverb/Pusher) configured in the host app, the bell listens over websockets. Register the channel authorization in your routes/channels.php:
Broadcast::channel('notifications.{userId}', fn ($user, $userId) => (int) $user->id === (int) $userId);
If Echo is absent, the component keeps working via polling.
REST API
For mobile apps and SPAs:
'api' => [ 'enabled' => true, 'prefix' => 'api/notifications', 'middleware' => ['api', 'auth:sanctum'], ],
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/notifications |
Paginated list (?status=unread|read|archived|pinned, ?type=, ?category=, ?search=) |
| GET | /api/notifications/unread-count |
Unread counter |
| GET | /api/notifications/stats |
Totals by state and type |
| POST | /api/notifications/{id}/read |
Mark as read |
| POST | /api/notifications/read-all |
Mark all as read |
| POST | /api/notifications/{id}/pin |
Toggle pin |
| POST | /api/notifications/{id}/archive |
Archive |
| POST | /api/notifications/{id}/unarchive |
Unarchive |
| DELETE | /api/notifications/{id} |
Soft delete |
| POST | /api/notifications/{id}/restore |
Undo delete |
| GET | /api/notifications/preferences |
Get user preferences |
| PUT | /api/notifications/preferences |
Update user preferences |
All endpoints operate strictly on the authenticated user's own notifications.
History page
A full-page notification center at /notifications/history with search, filters (status, type, category, period), pin/archive actions and incremental loading. Linked from the bell's "View all" footer. Disable with 'features' => ['history_page' => false].
Admin panel
Comprehensive management panel at /notifications (configurable): create/edit/delete, filters, send to one or all users, bulk actions, stats — fully redesigned with self-contained CSS.
'route' => [ 'prefix' => 'admin/notifications', 'middleware' => ['web', 'auth', 'role:admin'], 'name' => 'admin.notifications.', ],
Localization
Ships with English (default) and Brazilian Portuguese. By default the component follows app()->getLocale(), falling back to English. To force a language regardless of the app locale:
// config/notifications.php 'locale' => 'pt_BR', // null = follow the app locale
Or per component instance:
<livewire:notification-bell locale="pt_BR" />
This applies to every text in the bell, history page and admin panel — including relative dates ("há 4 meses" instead of "4 months ago"). To customize the strings:
php artisan vendor:publish --tag="notification-bell-lang"
Sounds
Generated with the Web Audio API (no audio files) — unique tones per type — or bring your own file:
'features' => [ 'sound' => [ 'enabled' => true, 'volume' => 0.5, 'file' => '/sounds/notify.mp3', // optional custom sound ], ],
Browsers may block audio until the user interacts with the page (standard autoplay policy).
Artisan commands
# Cleanup (respects keep_pinned / keep_archived, purges soft-deleted trash) php artisan notifications:cleanup php artisan notifications:cleanup --days=60 --unread-days=120 php artisan notifications:cleanup --dry-run # Bulk send php artisan notifications:send-bulk "Title" "Message" --all-users php artisan notifications:send-bulk "Maintenance" "Tonight 22h" --users=1,2,3 --type=warning
Schedule cleanup in routes/console.php (or Kernel.php on older versions):
Schedule::command('notifications:cleanup')->daily();
Queues
Notifications are processed through Laravel queues by default. Run a worker:
php artisan queue:work
Or bypass per call with 'queue' => false in the options array.
Testing
composer install
composer test
The suite uses Orchestra Testbench with in-memory SQLite — no app scaffolding required.
Deploy safety
The package is designed to never take a production page down:
- Migrations are idempotent and reconciling — partial previous runs, pre-existing tables or re-runs never fail the deploy; missing columns are added individually.
- Schema-readiness guard — between
composer updateandphp artisan migrate, the bell detects the outdated schema and renders empty (logging a warning) instead of throwing aQueryExceptionon every page. It recovers automatically on the first request after the migration runs. - Queued deliveries hitting a stale schema fail and retry through the queue — no notification is silently lost.
Feature toggles
Every user-facing capability can be turned off individually:
'features' => [ 'pin' => true, 'archive' => true, 'clear_all' => true, 'undo_delete' => ['enabled' => true, 'window' => 8000], 'grouping' => ['enabled' => true, 'min_size' => 3], 'history_page' => true, // toasts and sound: see above ],
Upgrading from 1.x
- If you had published the config before, re-publish it with
php artisan vendor:publish --tag="notification-bell-config" --forceto see the new keys (locale,theme,preferences,categories,deduplication,rate_limit,api...). An old published config keeps working — missing keys fall back to the package defaults. - New migrations add columns (
category,group_key,dedup_key,image_url,pinned_at,archived_at,scheduled_at,expires_at,deleted_at) and thenotification_preferencestable — just runphp artisan migrate. Notification::delete()is now a soft delete. Cleanup routines purge trashed rows.- The helper signature is backward compatible; new capabilities live in the trailing
$optionsarray. - The old Tailwind-dependent views were replaced by self-contained CSS. If you had published the views, re-publish or port your customizations to
theme.item_view/theme.bell_icon.
License
MIT License - see the LICENSE file for details.