To store notifications in the database in Laravel 13, run php artisan make:notifications-table and migrate, create a notification class whose via() returns ['database'], and send it with $user->notify(new YourNotification()). Read them back with $user->unreadNotifications.
This tutorial builds an "order shipped" notification, lists unread items, marks them as read and covers the mistakes that make notifications disappear.
What do I need first?
- Laravel 13 (current stable at the time of writing) with a supported PHP version.
- The default
Usermodel, which already uses theNotifiabletrait. - A migrated database.
How do I create the notifications table?
php artisan make:notifications-table
php artisan migrateThe table stores the notification type (the class name), the notifiable model, a JSON data column and a nullable read_at timestamp. If your users use UUID or ULID primary keys, edit the migration and replace morphs with uuidMorphs or ulidMorphs before migrating.
How do I create a notification class?
php artisan make:notification OrderShipped<?php
namespace App\Notifications;
use App\Models\Order;
use Illuminate\Notifications\Notification;
class OrderShipped extends Notification
{
public function __construct(public Order $order)
{
}
public function via(object $notifiable): array
{
return ['database'];
}
public function toArray(object $notifiable): array
{
return [
'order_id' => $this->order->id,
'message' => 'Your order #'.$this->order->id.' has shipped.',
];
}
}The array returned by toArray() is encoded to JSON and saved in the data column. Keep it small and store ids plus display text, not whole models. If the same class also broadcasts to a websocket channel, define toDatabase() separately so each channel gets its own payload.
How do I send it?
$user->notify(new OrderShipped($order));You can also send to many users at once with the Notification facade:
use Illuminate\Support\Facades\Notification;
Notification::send($users, new OrderShipped($order));How do I read notifications?
$user = auth()->user();
$all = $user->notifications; // newest first
$unread = $user->unreadNotifications;
$read = $user->readNotifications;
$count = $user->unreadNotifications()->count();
foreach ($unread as $notification) {
echo $notification->data['message'];
}How do I show them in a controller and view?
public function index(Request $request)
{
return view('notifications.index', [
'notifications' => $request->user()->notifications()->paginate(15),
]);
}@foreach ($notifications as $notification)
<div class="{{ $notification->read_at ? '' : 'fw-bold' }}">
{{ $notification->data['message'] }}
<small>{{ $notification->created_at->diffForHumans() }}</small>
</div>
@endforeach
{{ $notifications->links() }}Reading through the notifications() relationship scopes the query to the signed-in user, so one user cannot read another user's notifications.
How do I mark notifications as read?
// one notification
$request->user()->notifications()->findOrFail($id)->markAsRead();
// every unread notification
$request->user()->unreadNotifications->markAsRead();
// bulk update without loading models
$request->user()->unreadNotifications()->update(['read_at' => now()]);Use the last form for large lists because it runs a single SQL update. To remove them entirely, call $user->notifications()->delete().
Should I queue the notification?
Writing one database row is cheap, so queueing is optional. If the notification also sends email or SMS, add implements ShouldQueue and the Queueable trait so slow channels do not block the request. Run a worker with php artisan queue:work.
What are the common mistakes?
- Forgetting the table. The error "Base table or view not found: notifications" means you did not run the command and migration.
- Returning models from toArray(). Store plain values only.
- Using the wrong notifiable. The model must use the
Notifiabletrait. - Queued notification with no worker. Nothing is stored until a worker runs.
- Exposing all notifications. Always read them through the authenticated user.
How do I verify it works?
use Illuminate\Support\Facades\Notification;
public function test_order_shipped_notification_is_sent(): void
{
Notification::fake();
$user = User::factory()->create();
$order = Order::factory()->create();
$user->notify(new OrderShipped($order));
Notification::assertSentTo($user, OrderShipped::class);
}For a real check, send one in php artisan tinker and then run $user->unreadNotifications->count().
FAQ
Where are the notifications stored?
In the notifications table, with the payload in the data JSON column.
Can one class use several channels?
Yes. Return ['database', 'mail'] from via() and add a toMail() method.
How do I change the stored type?
Define a databaseType() method on the notification to return your own string.



