K

Modules

The Notifications Module

The history behind the notification bell — the same rows with a table over them, scoped to the viewer by default.

On this page

The bell's panel shows the latest few. This is the same rows with a table over them: searchable, filterable by read state, and readable after they have scrolled out of the panel.

composer require nyoncode/wire-module-notifications
php artisan wire-module-notifications:install

How It Works

Scoped to the viewer. A notification is addressed to somebody, so the default shows the signed-in user's own. Signed out, that means none — the tempting alternative, no filter at all, is a leak. scope => 'all' turns it into an administrative view, which is a different screen and wants a policy on its page.

Opening one marks it read, in the page's own record hook, because that is what every inbox has always done and the unread count is then right without anyone pressing anything.

It is not in the sidebar, and that is the design. Notifications are not an entity you administer from a menu — they are an inbox, and the way in is the bell: it carries the unread count, and its panel links straight here. A permanent row for a screen you reach from a badge is the row nobody reads. The page stays registered and routed either way, so its URL keeps working; set navigation.visible (or WIRE_NOTIFICATIONS_IN_NAVIGATION=true) if you want the row as well.

Only the database driver stores anything. Under the default session driver there is nothing to list, and the installer says so.

Configuration

// config/wire-module-notifications.php
'model' => NyonCode\WireCore\Notifications\DatabaseNotification::class,
 
'scope' => 'own', // 'own' the viewer's own rows; 'all' everyone's
 
'navigation' => [
'visible' => false, // the inbox is reached from the bell, not from a menu row
'group' => 'system',
'label' => null, // null uses the module's own group heading
'icon' => 'outline:bell',
'sort' => 96,
],

model is the class the screen lists, and it is wire-core's: this module owns the screen, not the table. A model of your own must extend that one — the rows are written by the notification driver, which knows nothing about this module.

scope => 'all' turns an inbox into an administrative view of everyone's mail, which wants a permission on the page rather than a config key alone. Signed out, own resolves to nothing at all, which is the safe half of the choice.

The wording is a published translation file and the markup a published view — wire-module-notifications::translations and …::views, with what each costs in Theming → Localization and Overriding Views.

What You Get

Screen Notes
Notifications The notification with the line it was written to say, tinted by type, newest first; relative times; search over the payload; filter unread or read
One notification Its type, its times and the payload as key/value

It reads as an inbox rather than as a table of columns, and the choices behind that are worth stating because they are easy to undo by accident:

  • Unread is weight, not a cell. The row is tinted and set in medium, the same way the bell's panel says it — so the two surfaces read as one product, and the column a STATE chip used to occupy is the one the message now uses.
  • The search reads the payload. The visible text lives in the JSON data column, so the search is declared over data->title and data->message. A search box that finds nothing is worse than no search box.
  • The row verbs sit behind one quiet trigger. Mark read, mark unread, delete. A page whose whole job is to be read should not be dominated by a solid blue button and a solid red one on every line.
  • It is not drawn as a table. layout('list'), and with it the three controls that only mean something over a grid of columns: no checkbox on every row, no column panel, no Show [10] records. Marking everything read is a header action over the whole filtered set, which is stronger than a selection and quieter than one.
  • The row opens the notification's own page, which is what marks it read — deliberately its own page rather than whatever ->url() points at, because a list whose rows sometimes go to an invoice and sometimes to a notification is a list you cannot click confidently. The payload's link is on the page it opens.

Selecting rows offers the three verbs of an inbox — mark read, mark unread and delete. Nothing there re-scopes: the selection comes out of the same query the table lists, which is already the viewer's own rows, so a bulk action cannot reach further than the screen it was started from.

  • Notifications — the drivers, the bell, the live half and the toasts
  • Modules — how a package ships an area like this