Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Notification Feed Laravel Package

coreproc/nova-notification-feed

A Laravel Nova package that adds a notification feed to your admin panel, with unread counts, a slide-out feed, and read/unread management. Built for Nova 4.

View on GitHub
Deep Wiki
Context7

Getting Started

Preflight (10 minutes, before you composer require anything). This package's last release is 1.4.0 from March 2020 — it targets Nova 1–3, Laravel 5.8–7.x, and Vue 2. Two checks decide everything:

# Does it even resolve against your lockfile?
composer require coreproc/nova-notification-feed --dry-run

# What Nova major are you on?
composer show laravel/nova | grep versions

If you're on Nova 4+ (Vue 3), stop here and read the "Build-thin" note at the bottom of this section. If you're on Nova ≤3, continue.

Install. Vendor it, don't just require it — the pin is the point.

composer require coreproc/nova-notification-feed:^1.4
php artisan vendor:publish --provider="CoreProc\NovaNotificationFeed\ToolServiceProvider"
php artisan migrate

Run the publish command without --force first and read the list of tags it offers — you want the config and assets, and you want to know whether the migration tag collides with the notifications table you (probably) already have from php artisan notifications:table. If it does, discard the migration and keep yours; the package reads the standard schema (id, type, notifiable_type, notifiable_id, data, read_at, created_at, updated_at).

Register the tool. In app/Providers/NovaServiceProvider.php:

public function tools()
{
    return [
        new \CoreProc\NovaNotificationFeed\NovaNotificationFeed(),
        // ...
    ];
}

Make sure the receiving model notifies. The bell renders for Auth::user(), and the query scopes on notifiable_type / notifiable_id. That means the model the feed reads from must use Laravel's Notifiable trait and must actually be the notifiable in your notification classes:

class User extends Authenticatable
{
    use Notifiable; // required — this is what the feed queries against
}

First use case — the one that pays for the install. Prove the bell shows something real. Send yourself a database notification and watch it appear:

php artisan tinker
>>> $u = App\Models\User::find(1);
>>> $u->notify(new App\Notifications\TestDatabaseNotification());

Reload Nova. If the bell renders with a count, you've validated the whole chain: migration, asset compilation, tool registration, and the data payload. If it doesn't render, the failure is almost always asset compilation (check public/vendor/nova-notification-feed exists and the browser console for a Vue error), not the PHP side.

Where to look first, in this order:

  1. vendor/coreproc/nova-notification-feed/src/ — the tool class, the controller/model doing the query, and the config default.
  2. vendor/coreproc/nova-notification-feed/resources/js/ — the Vue 2 components. This is where the payload shape contract lives, and it's the thing you'll actually need to read before standardizing anything.
  3. config/nova-notification-feed.php (after publish) — poll interval, pagination limit, notification model class.
  4. composer.json in the vendor dir — the exact laravel/nova and PHP constraints. This is your upgrade tripwire.

If you're on Nova 4/5: don't shim it. The Vue 2 → Vue 3 migration of a third-party tool with zero dependents and no upstream is a sprint-eating trap. Build a ~150-line Nova Resource over the notifications table instead — a read_at column, a notifiable filter, and an index on (notifiable_type, notifiable_id, read_at, created_at). Cite this package as prior art in the ticket. You'll have the same support-deflection benefit on a supported stack.

Implementation Patterns

Standardize the payload before you standardize the UI. The single highest-leverage thing you can do day-to-day is enforce a contract on toDatabase() across every notification class. The feed renders data as-is; heterogeneous payloads produce a dropdown where half the rows are unreadable JSON blobs.

public function toDatabase($notifiable): array
{
    return [
        'title'   => 'Order #1042 confirmed',
        'body'    => 'Payment captured, fulfillment queued.',
        'url'     => route('nova.resources.detail', ['resource' => 'orders', 'resourceId' => $this->order->id]),
        'icon'    => 'check-circle',
        'level'   => 'success', // custom key — read it in your component override
    ];
}

Put this in an abstract DatabaseNotification base class and have notification classes extend it. Then a new developer adding a notification type gets a correct feed row for free, and the feed stops being a debugging tool that only works for the two types you tested.

Verification loop for support tickets. The core day-to-day workflow is: support says "user X never got the password reset email," and you need to answer queued, sent, failed, or never created? The feed answers the "was it created and when" half.

// Triage in tinker, faster than clicking:
User::find($id)->notifications()->latest()->take(10)->get()
    ->map(fn ($n) => [$n->type, $n->created_at, $n->read_at]);

Note this shows created notifications, not delivered ones. If via() includes mail, the row still exists even when the mail send failed. Pair the feed with your queue's failed-jobs table — the bell is not a delivery log and shouldn't be pitched as one.

Local development. Set QUEUE_CONNECTION=sync while iterating on notification classes, otherwise you'll be reloading the bell wondering why nothing appears. In staging/production with real queues, remember the bell polls — a notification triggered in a request won't show until the worker processes it and the next poll fires.

Custom rendering per type. If the feed supports a component override (check resources/js/ for the registry), map known type strings to components and fall back to a generic "you have a notification" row. If it doesn't support overrides, the pragmatic path is to keep data uniformly shaped and do all the differentiation in the payload — cheaper than maintaining a fork of the Vue layer.

// Defensive: notification types written by older code or other packages
// may have missing keys. Never let one bad row break the whole feed.
$title = data_get($notification->data, 'title')
    ?? class_basename($notification->type);

Broadcasting vs. polling. 1.4.0 bumped pusher/pusher-php-server from ^3.2 to ^4.1.1 — a signal that broadcast support exists. For internal ops at modest traffic, leave polling on and skip the Pusher dependency entirely; fewer moving parts. If you do enable broadcasting, make sure the broadcast channel is authorized against the authenticated user's ID so user A can't subscribe to user B's channel — that's the one place a copy-paste mistake becomes a data leak.

Retention is your job, not the package's. Unread counts are a count() over the notifications table scoped to the user. That table grows forever by default. Ship a prune job in the same sprint as the install:

// Read notifications older than 90 days — adjust to your audit needs
Notification::whereNotNull('read_at')
    ->where('read_at', '<', now()->subDays(90))
    ->delete();

Also add the index. If your existing notifications migration has only the default (notifiable_type, notifiable_id) index, queries filtering by read_at for the badge will degrade quietly over months rather than loudly.

Testing. Don't test the package; test your own contract.

Notification::fake();
$user->notify(new OrderConfirmed($order));
Notification::assertSentTo($user, OrderConfirmed::class, function ($n) use ($order) {
    return data_get($n->toDatabase($user), 'title') !== null;
});

And add one smoke test that the tool is registered in NovaServiceProvider::tools() — cheap insurance against a silent removal during a refactor.

When you need "view as user." The feed is self-scoped to Auth::user(). The moment a support agent asks "show me Bob's feed," you've outgrown it. Rather than forking the Vue layer, add a separate read-only Nova Resource on the notifications model with a notifiable relationship filter, and leave the bell as the personal view. Two surfaces, one table, no fork.

Gotchas and Tips

The bell disappears silently. This is the defining failure mode. Nothing errors — the tool's JS simply fails to compile or register after a Nova upgrade, and the icon stops appearing. There is no runtime warning the end user will report. Add a CI check that the compiled asset exists after nova:publish/asset build, and a health-check route or artisan command that asserts the tool class is in NovaServiceProvider::tools(). Without this, you find out weeks later from a support ticket.

Read/unread semantics are not what your product team thinks. read_at means seen in a dropdown, not acknowledged or acted upon. If someone asks for "mark as resolved," do not overload read_at. Add a separate column or a separate concept. This is the most common way a simple feed turns into a schema argument three months in.

Badge/list drift. If marking-as-read is optimistic in the UI (which it almost certainly is), the badge count and the list can disagree after a failed request. Any custom read-marking endpoint you add must return the authoritative unread count, and the component should reconcile rather than decrement locally. On a flaky network this is visible and confusing.

Migration collision on deploy. If the package's published migration creates a notifications table and you already have one, php artisan migrate fails at deploy time — in production, mid-deploy. Test the publish + migrate on a branch against a schema dump of production, not against a fresh migrate:fresh. Verify pre-merge, not pre-deploy.

Polling storms during incidents. Polling scales with concurrent admin sessions, which is worst exactly when something is broken and every operator has the panel open. Find the interval in the published config and make it settable via env so you can dial it down (or disable the tool) during an incident instead of discovering the load source live.

vendor:publish --force overwrites your edits. You will end up customizing the published config and possibly the Vue components. Once you do, upstream is dead to you — record that in the README and stop pretending you can composer update this package. Vendor a pinned commit into the monorepo, name an owner, and treat it as first-party code. That's not a caveat; that's the operating model.

Multi-tenant scoping is inherited, not guaranteed. The feed is safe because it filters on notifiable_type/notifiable_id and you call it as Auth::user(). It stops being safe the moment you add an admin view, an export, or a "notifications for this organization" query. Any new query surface you build over this table needs an explicit tenant scope — don't assume the package's safety carries over.

Wrap rendering in a fallback. A single notification with an unexpected data shape (a null, a JSON string instead of an array, a key that was renamed two releases ago) should degrade to a generic row, not blank the dropdown. Put the data_get(...) ?? class_basename($notification->type) fallback in the payload layer so every consumer benefits.

Extension points worth knowing before you fork: the published config (interval, limit, model class), the component registry in resources/js/ (per-type rendering), and the trait on User (read marking). Everything else — the query, the controller — you should consider frozen unless you're committing to ownership. If your needs touch more than those three, you're not customizing, you're forking, and you should price it as a fork (half a day to two days per Nova major, entirely in Vue 2 component work) before you start.

Ramp-up reality. A mid-level Laravel developer can make their first payload customization in an hour. The constraint isn't Laravel — it's Vue 2, which is increasingly scarce and deprioritized on modern teams. If your frontend capacity is Vue 3-only, that constraint should push you toward the thin Nova Resource build even on a Nova 3 stack.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package