DEV3 Technologies home

Filament notifications with action buttons

Send a Filament 4 database notification with View and Acknowledge buttons, mark-as-read behaviour and a one-time signed link.

FilamentNotificationsfilamentnotificationsactionssigned-url

Code

<?php

namespace App\Notifications;

use App\Filament\Resources\Incidents\IncidentResource;
use App\Models\Incident;
use App\Models\User;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\URL;

class IncidentNotifier
{
    /** Sends to the bell menu of each on-call user, and as a toast to the current user. */
    public static function opened(Incident $incident, Collection $recipients): void
    {
        $notification = Notification::make()
            ->title("Incident: {$incident->title}")
            ->body("Affects {$incident->monitor->name}. Started ".$incident->started_at->diffForHumans().'.')
            ->icon('heroicon-o-exclamation-triangle')
            ->iconColor('danger')
            ->persistent();

        $recipients->each(function (User $user) use ($incident, $notification) {
            // Actions are built per user because the signed link is bound to the incident and the user id.
            $notification->actions([
                Action::make('view')
                    ->label('View incident')
                    ->button()
                    ->url(IncidentResource::getUrl('view', ['record' => $incident]), shouldOpenInNewTab: true)
                    ->markAsRead(),
                Action::make('acknowledge')
                    ->label('Acknowledge')
                    ->color('gray')
                    ->url(URL::temporarySignedRoute(
                        'incidents.acknowledge',
                        now()->addHours(6),
                        ['incident' => $incident, 'user' => $user],
                    ))
                    ->markAsRead(),
            ]);

            $notification->sendToDatabase($user);
        });
    }
}

Needs: filament/filament@^4.0

How it works

What it solves

A plain notification tells people something happened but leaves the next step to them. Buttons inside the notification, "View" and "Acknowledge", turn an alert into a two-click workflow straight from the bell menu.

How it works

IncidentNotifier::opened() builds a Filament Notification with a title, body, warning icon and persistent() so it stays until dismissed. For each recipient it attaches Action objects and calls sendToDatabase().

The View button is a normal link to the incident resource page, opened in a new tab, and markAsRead() clears the unread state when it is clicked. The Acknowledge button points at a temporary signed route valid for six hours. The user id is part of the signed URL, and the controller refuses anybody else.

The controller keeps the operation idempotent with syncWithoutDetaching, so double clicks and repeated visits cause no duplicate rows. It then shows a success toast and returns to the panel.

Adapting it

Swap the model, title and route names. For quick in-page responses without a redirect, use Action::dispatch('event-name') and a Livewire listener on pages that are always loaded; the signed URL works everywhere, including emails. Add ->sendToDatabase($user, isEventDispatched: true) if the bell should update instantly with broadcasting.

Pitfalls

  • Database notifications require php artisan make:notifications-table and ->databaseNotifications() on the panel.
  • A GET request that changes state is only acceptable because it is signed, idempotent and user-bound.
  • Rebuilding actions inside the loop mutates the same object; send in the same iteration, as shown.

How to use it

  1. Run php artisan make:notifications-table && php artisan migrate, and add ->databaseNotifications() to the panel.
  2. Add the notifier and controller, then register the signed incidents.acknowledge route.
  3. Create an acknowledgedBy many-to-many relation on Incident with an acknowledged_at pivot column.
  4. Call IncidentNotifier::opened($incident, $onCallUsers) when an incident starts.
  5. Open the bell menu in the panel to see both buttons.

Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.

Related snippets