coreproc/nova-echo
coreproc/nova-echo integrates Laravel Echo with Laravel Nova, enabling real-time broadcasting and live updates for Nova resources, dashboards, and custom tools. It helps admin panels listen for broadcast events and refresh without polling, making interfaces more responsive.
1. Confirm version fit before anything else. The last tagged release is March 2023, which predates Nova 4.x's Vite-based asset pipeline settling and Nova 5. Check composer.json in the package for its Nova constraint, then check your nova version:
composer show coreproc/nova-echo
composer show laravel/nova
If the constraint excludes your Nova, try dev-main and expect to patch the tool's JS build. This is a small package (19 stars), so budget an afternoon for asset debugging.
2. Install and publish.
composer require coreproc/nova-echo
php artisan vendor:publish --provider="Coreproc\NovaEcho\NovaEchoServiceProvider"
Read vendor/coreproc/nova-echo/README.md immediately — it is shorter than you expect and is the only authoritative source for the trait/field method names (echoChannel(), channels(), NovaEcho::make(), etc. vary between versions). The published config file holds the channel prefix/event mapping.
3. Get Echo working in Nova's frontend first, before adding the package. The package assumes a live Echo instance exists in the Nova bundle. Set up your broadcaster (Pusher, Ably, or Reverb), publish and build Nova's assets, and verify with a console.log(window.Echo) in a Nova page. If Echo isn't there, nothing in this package will work and the failure mode is silent.
BROADCAST_DRIVER=pusher
PUSHER_APP_KEY=...
PUSHER_APP_CLUSTER=mt1
php artisan nova:publish
# then, in your Nova installation directory
npm install && npm run dev
4. First use case — pick a high-churn index, not everything. The best pilot is an index that a support or ops team stares at: an Orders queue, a failed-jobs view, an audit log. Wire one resource, watch it for a day, and measure refetch traffic before rolling it out. Dashboard-style resources with steady created events are where this package earns its keep; static reference-data resources are not.
5. Make sure the queue is running. If your broadcast events are queued (the Laravel default for ShouldBroadcast), a dead queue:work looks identical to a broken Nova integration. Start there when debugging.
The typical shape is a trait on the Nova resource plus a channel definition, with the model broadcasting standard events:
// app/Nova/Order.php
use Coreproc\NovaEcho\Concerns\NovaEcho; // verify exact namespace in README
class Order extends Resource
{
use NovaEcho;
public static function echoChannel(): string
{
return 'orders';
}
}
// app/Models/Order.php
class Order extends Model
{
protected $dispatchesEvents = [
'created' => OrderCreated::class,
'updated' => OrderUpdated::class,
'deleted' => OrderDeleted::class,
];
}
Keep the channel name derivable from a single helper ("orders.{$tenantId}") rather than hardcoding it in three places — resource, event, and config.
Index and detail views behave differently and should be treated as different features:
Test both by opening two browser windows side by side. That's the only test that catches the "row appears twice after edit" class of bug.
Because the package reissues Nova's own request, scoped listings stay scoped only if the request is rebuilt from window.location.search. When you extend or wrap the package's JS, preserve the query string rather than hardcoding /nova-api/orders. If you find the package appending rather than refetching, your fallback is a debounced this.$router.replace(...)-style refresh inside a small Nova card, which is ~40 lines and gives you full control.
For multi-tenant admins, never broadcast on a global channel. Scope it:
public static function echoChannel(): string
{
return 'tenants.' . auth()->user()->tenant_id . '.orders';
}
Return a PrivateChannel from the broadcast event and make sure Nova's /broadcasting/auth is reachable with the same session — Nova uses the web guard by default, so private channel auth works out of the box on a single domain. Subdomain-per-tenant setups need SESSION_DOMAIN=.yourdomain.com or you'll get 403s on the auth handshake.
The most valuable pattern is a hybrid: use the broadcast to show a Nova toast ("3 new orders") and let the index refetch on a debounce, so a burst of 50 events doesn't cause 50 refetches. Wire a separate Echo listener in a custom Nova card or Nova::script() and use the package purely for the structural refresh.
Broadcasting is easy to fake and easy to forget to fake:
Event::fake([OrderCreated::class]);
$this->post('/nova-api/orders', [...])->assertSuccessful();
Event::assertDispatched(OrderCreated::class);
For the frontend, you generally can't unit-test the Nova asset; rely on a two-window manual check in a staging environment with a real broadcaster.
Nova 5 / asset pipeline breakage is the most likely blocker. A March 2023 package was built against Nova's earlier asset setup. If nova:publish + npm run build throws on the package's JS entry, you'll need to patch node_modules/vendor and pin via a fork. Confirm the build succeeds before writing any resource code.
Broadcasts fire before the transaction commits. This is the number one source of "the UI shows stale data" reports. If you wrap writes in DB::transaction, the broadcast can beat the commit and Nova refetches the old row. Fix with public $afterCommit = true; on the event, or ->afterCommit() / DB::afterCommit(fn () => broadcast(...)).
Private channel auth silently fails. A 403 on /broadcasting/auth produces an empty console error and no UI change. Check the Network tab first for any "updates aren't arriving" report — it is almost always auth, not the package.
Burst events cause a refetch storm. 200 imports in 30 seconds means 200 Nova API round-trips per open browser tab. Debounce client-side (300–800ms) or batch server-side by dispatching a single OrdersBulkUpdated event. Watch your Pusher/Ably message volume and your Nova API logs on day one.
"Works with search/filters" is a claim to verify, not assume. Explicitly test: filter to status=open, then broadcast a closed order. The new record must not appear. If it does, the package is appending and you need the refetch fallback.
Soft deletes and restores. deleted events on soft-delete models don't mean the row disappears from the index — it depends on your SoftDeletes scope and whether the package listens for restored. Test both.
Only refresh when the tab is visible. Background tabs refetching every event is wasted load. Wrap your listener in document.visibilityState === 'visible' or have the package's refetch gated on focus; a broadcast missed while hidden is recovered by the refetch on focus.
Don't treat it as a consistency guarantee. It's a UI convenience layer, not a real-time source of truth. Users can still have a stale row if a broadcast is dropped or the socket reconnects mid-session. Any workflow where acting on stale data is dangerous (approvals, balances) needs a server-side check on submit.
Reconnection leaves a gap. Echo reconnects after a network blip but does not replay missed events. Add a periodic background refresh (every 60–120s) as a safety net; it costs little and closes the gap.
Extension points are limited, so keep your additions outside the package. Rather than editing vendor files, override the channel method in the resource, wrap the behaviour in your own Nova card, or use Nova::script() alongside. That way composer update doesn't wipe your work — which matters a lot for a low-maintenance package.
Check the repo's issue tracker before adopting on a critical dashboard. With ~19 stars and no release since early 2023, expect to own the maintenance. If you need more than index/detail refresh — per-row highlighting, optimistic updates, presence indicators — you'll get there faster writing a small custom Nova card around laravel-echo directly than extending this one.
How can I help you explore Laravel packages today?