CMS Framework - v2.2.2
Notifications — Managing Notifications
This guide covers retrieving, reading, and dismissing notifications for users.
Notification States
Each user has their own state for every notification they receive, tracked in the notification_user pivot table:
- Unread —
is_read = false, is_dismissed = false - Read —
is_read = true, is_dismissed = false - Dismissed —
is_dismissed = true(hidden from user's list)
Retrieving Notifications
Using User Model Methods
The HasNotifications trait provides convenient relationship methods:
$user = auth()->user();
// All notifications for this user
$all = $user->systemNotifications;
// Only unread notifications
$unread = $user->unreadSystemNotifications;
// Count unread
$count = $user->unreadSystemNotificationsCount();
Using Helper Functions
use function apGetNotifications;
use function apGetUnreadNotificationCount;
// Get notifications for user ID 1
$notifications = apGetNotifications($userId, $limit = 10, $unreadOnly = false);
// Get only unread
$unread = apGetNotifications($userId, $limit = 10, $unreadOnly = true);
// Get unread count
$count = apGetUnreadNotificationCount($userId);
Accessing Notification Data
$user = auth()->user();
foreach ($user->systemNotifications as $notification) {
echo $notification->id; // Notification ID
echo $notification->title; // Title
echo $notification->content; // Content
echo $notification->type->label(); // "Success", "Error", etc.
echo $notification->metadata; // Array of custom data
echo $notification->created_at; // Carbon timestamp
// Access pivot data (user-specific state)
echo $notification->pivot->is_read;
echo $notification->pivot->read_at;
echo $notification->pivot->is_dismissed;
echo $notification->pivot->dismissed_at;
}
Marking as Read
Mark Single Notification
Using the User model:
$user = auth()->user();
$notificationId = 123;
$success = $user->markNotificationAsRead($notificationId);
if ($success) {
// Notification marked as read
} else {
// Failed (notification not found or doesn't belong to user)
}
Using helper functions:
use function apMarkNotificationAsRead;
apMarkNotificationAsRead($notificationId, $userId);
Mark All as Read
Using the User model:
$user = auth()->user();
$count = $user->markAllNotificationsAsRead();
echo "Marked {$count} notifications as read";
Using helper functions:
use function apMarkAllNotificationsAsRead;
$count = apMarkAllNotificationsAsRead($userId);
Read State
When marked as read:
is_readis set totrueread_atis set to current timestamp- The notification remains visible in the user's list
- The
ap.notifications.readNotificationaction fires
Dismissing Notifications
Dismissing removes a notification from the user's visible list:
Dismiss Single Notification
Using the User model:
$user = auth()->user();
$notificationId = 123;
$success = $user->dismissNotification($notificationId);
if ($success) {
// Notification dismissed
}
Using helper functions:
use function apDismissNotification;
apDismissNotification($notificationId, $userId);
Dismiss All Notifications
Using the User model:
$user = auth()->user();
$count = $user->dismissAllNotifications();
echo "Dismissed {$count} notifications";
Using helper functions:
use function apDismissAllNotifications;
$count = apDismissAllNotifications($userId);
Dismissed State
When dismissed:
is_dismissedis set totruedismissed_atis set to current timestamp- The notification is hidden from queries by default
- The
ap.notifications.dismissNotificationaction fires
Query Scopes
The Notification model includes helpful query scopes:
Unread for User
use ArtisanPackUI\CMSFramework\Modules\Notifications\Models\Notification;
$unread = Notification::unreadForUser($userId)->get();
Read for User
$read = Notification::readForUser($userId)->get();
Not Dismissed for User
$active = Notification::notDismissedForUser($userId)->get();
By Type
use ArtisanPackUI\CMSFramework\Modules\Notifications\Enums\NotificationType;
$errors = Notification::ofType(NotificationType::Error)
->notDismissedForUser($userId)
->get();
Combining Scopes
// Get unread error notifications
$unreadErrors = Notification::unreadForUser($userId)
->ofType(NotificationType::Error)
->orderByDesc('created_at')
->limit(5)
->get();
Practical Examples
Display Notification Bell Icon
@php
$unreadCount = auth()->user()->unreadSystemNotificationsCount();
@endphp
<div class="notification-bell">
<i class="fas fa-bell"></i>
@if($unreadCount > 0)
<span class="badge">{{ $unreadCount }}</span>
@endif
</div>
Notification Dropdown
@php
$notifications = auth()->user()->systemNotifications()->limit(5)->get();
@endphp
<div class="notifications-dropdown">
@forelse($notifications as $notification)
<div class="notification-item {{ $notification->pivot->is_read ? 'read' : 'unread' }}">
<div class="icon {{ $notification->type->colorClass() }}">
<i class="{{ $notification->type->icon() }}"></i>
</div>
<div class="content">
<h4>{{ $notification->title }}</h4>
<p>{{ $notification->content }}</p>
<small>{{ $notification->created_at->diffForHumans() }}</small>
</div>
<div class="actions">
@unless($notification->pivot->is_read)
<button wire:click="markAsRead({{ $notification->id }})">
Mark as read
</button>
@endunless
<button wire:click="dismiss({{ $notification->id }})">
Dismiss
</button>
</div>
</div>
@empty
<div class="no-notifications">
No notifications
</div>
@endforelse
@if($notifications->count() > 0)
<div class="notification-actions">
<button wire:click="markAllAsRead">Mark all as read</button>
<button wire:click="dismissAll">Clear all</button>
</div>
@endif
</div>
Livewire Component
use Livewire\Component;
use ArtisanPackUI\CMSFramework\Modules\Notifications\Models\Notification;
class NotificationDropdown extends Component
{
public function markAsRead($notificationId)
{
auth()->user()->markNotificationAsRead($notificationId);
$this->dispatch('notification-updated');
}
public function dismiss($notificationId)
{
auth()->user()->dismissNotification($notificationId);
$this->dispatch('notification-updated');
}
public function markAllAsRead()
{
auth()->user()->markAllNotificationsAsRead();
$this->dispatch('notification-updated');
}
public function dismissAll()
{
auth()->user()->dismissAllNotifications();
$this->dispatch('notification-updated');
}
public function render()
{
return view('livewire.notification-dropdown', [
'notifications' => auth()->user()->systemNotifications()->limit(10)->get(),
'unreadCount' => auth()->user()->unreadSystemNotificationsCount()
]);
}
}
API Controller Actions
use Illuminate\Http\Request;
class UserNotificationController extends Controller
{
public function markAsRead(Request $request, int $id)
{
$success = $request->user()->markNotificationAsRead($id);
return response()->json([
'success' => $success,
'unread_count' => $request->user()->unreadSystemNotificationsCount()
]);
}
public function dismiss(Request $request, int $id)
{
$success = $request->user()->dismissNotification($id);
return response()->json(['success' => $success]);
}
public function markAllAsRead(Request $request)
{
$count = $request->user()->markAllNotificationsAsRead();
return response()->json([
'count' => $count,
'unread_count' => 0
]);
}
}
Pagination
For large notification lists, use pagination:
$notifications = Notification::notDismissedForUser($userId)
->orderByDesc('created_at')
->paginate(20);
return view('notifications.index', compact('notifications'));
Filtering by Type
Allow users to filter by notification type:
use ArtisanPackUI\CMSFramework\Modules\Notifications\Enums\NotificationType;
$type = request('type');
$query = Notification::notDismissedForUser($userId);
if ($type) {
$query->ofType(NotificationType::from($type));
}
$notifications = $query->orderByDesc('created_at')->paginate(20);
Soft Delete vs Dismiss
The module uses dismissal rather than deletion:
- Dismissed notifications remain in the database
- Useful for audit trails and analytics
- Users can potentially "undo" dismissals (with custom implementation)
If you need to permanently delete:
// Delete dismissed notifications older than 90 days
Notification::whereHas('users', function ($q) {
$q->where('is_dismissed', true)
->where('dismissed_at', '<', now()->subDays(90));
})->delete();
Real-Time Updates
For real-time notification updates, combine with Laravel Echo and broadcasting:
// In your event
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class NotificationSent implements ShouldBroadcast
{
public function __construct(public Notification $notification, public array $userIds)
{
}
public function broadcastOn(): array
{
return array_map(
fn($userId) => new PrivateChannel("users.{$userId}"),
$this->userIds
);
}
}
// Frontend
Echo.private(`users.${userId}`)
.listen('NotificationSent', (e) => {
// Update notification count
updateNotificationBadge();
// Show toast
showNotificationToast(e.notification);
});
Best Practices
Auto-Mark as Read
Consider automatically marking notifications as read when the user views them:
public function show(Notification $notification)
{
$user = auth()->user();
if (!$notification->pivot->is_read) {
$user->markNotificationAsRead($notification->id);
}
return view('notifications.show', compact('notification'));
}
Cleanup Old Notifications
Schedule a command to clean up old dismissed notifications:
// In App\Console\Kernel
protected function schedule(Schedule $schedule)
{
$schedule->command('notifications:cleanup')->monthly();
}
// Command
public function handle()
{
$deleted = Notification::whereDoesntHave('users', function ($q) {
$q->where('is_dismissed', false);
})->where('created_at', '<', now()->subMonths(6))->delete();
$this->info("Deleted {$deleted} old notifications");
}
Cache Unread Count
Cache the unread count to reduce database queries:
public function unreadCount()
{
return Cache::remember(
"user.{$userId}.unread_notifications",
300, // 5 minutes
fn() => apGetUnreadNotificationCount($userId)
);
}
// Invalidate cache when marking as read
public function markAsRead($notificationId)
{
$success = auth()->user()->markNotificationAsRead($notificationId);
if ($success) {
Cache::forget("user.{$userId}.unread_notifications");
}
return $success;
}
Next Steps
- Configure Notification Preferences for user control
- Use the API Reference to explore all available methods
- Learn about Hooks and Events to extend functionality
- Review Database and Migrations for schema details