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 Metric Polling Laravel Package

johnpaulmedina/nova-metric-polling

A Laravel Nova package that adds automatic polling to metric cards, keeping dashboards refreshed with live data without manual reloads. Configure polling intervals to display up-to-date metrics, trends, and progress values in your Nova admin panel.

View on GitHub
Deep Wiki
Context7

Getting Started

What it is: Laravel Nova metric cards refresh only when the dashboard loads or the user clicks the refresh icon. This package patches that gap by letting individual metric cards re-fetch themselves on a timer — useful for dashboards that sit on a wall monitor or that you watch during a deploy/incident.

Prerequisites: A licensed Laravel Nova install (this package is useless without it) and a Nova version whose card API matches the package's constraint. Check composer.json for the laravel/nova constraint before installing — the last tagged release is July 2023, which predates Nova 5.

Install:

composer require johnpaulmedina/nova-metric-polling

There is no service provider registration to do by hand (package discovery handles it), but if the card needs compiled JS you may need to publish assets:

php artisan vendor:publish --provider="JohnPaulMedina\NovaMetricPolling\NovaMetricPollingServiceProvider"

(Confirm the exact provider name from src/ — this package is small and lightly documented, so read the source rather than trusting the README alone.)

Where to look first:

  1. src/ — the metric base classes (value/trend/partition variants) and the trait or property that carries the interval.
  2. resources/js/ — the Vue card component that actually calls setInterval and re-issues the Nova metric request.
  3. composer.json — Nova version constraint and PHP constraint.
  4. The README's usage example — the API surface is probably one method/property, so the whole integration is a five-line diff.

First use case: Take an existing UsersPerDay extends Value metric on your main dashboard, switch the parent class to the package's equivalent, and return an interval in seconds. Load the dashboard, open the network tab, and confirm the metric endpoint is hit repeatedly at that cadence. That single card proves the wiring before you convert the rest.

Implementation Patterns

Swap the parent class, don't rewrite the metric. The whole point is that calculate(NovaRequest $request) is unchanged. Your diff should be:

// before
use Laravel\Nova\Metrics\Value;

// after
use JohnPaulMedina\NovaMetricPolling\Metrics\Value; // confirm exact namespace from src/

class ActiveUsers extends Value
{
    public function calculate(NovaRequest $request)
    {
        return $this->count($request, User::where('last_seen_at', '>=', now()->subMinutes(5)));
    }

    public function pollingInterval(): int
    {
        return 30; // seconds — verify property vs. method in the package source
    }
}

If the package exposes a trait instead of subclasses, the pattern is the same: use HasPolling; in your metric and set the interval.

Create a house base class. With 7 stars and no dependents, don't scatter the package's class names across dozens of metrics. Subclass once:

namespace App\Nova\Metrics;

abstract class PollingValue extends \JohnPaulMedina\NovaMetricPolling\Metrics\Value
{
    public function pollingInterval(): int
    {
        return 60;
    }
}

Now a future swap to a different polling package (or a Nova-native solution) is a one-file change.

Tier your intervals by query cost. A count() on an indexed column can poll at 15s. A trend over 90 days with a join should not. Rough tiers that work in practice: 10–15s for cheap counters, 60s for aggregates, 5min for anything hitting a large table unindexed. Stagger them — if six cards all poll on a 30s boundary they arrive as a burst.

Pair polling with Nova's cache, deliberately. $this->cacheFor(now()->addMinutes(5)) on a card polling every 30s means you make one DB hit per 5 minutes and serve 10 stale responses. That is usually what you want, but the two numbers must be chosen together — a cache TTL longer than the interval silently defeats the feature, and a TTL shorter than it defeats the cache.

Scope the metric to where it earns its keep. Polling exists for the "always-on" screen. Use ->onlyOnDetail(), or conditionally register the card in NovaServiceProvider::cards() behind a config flag, so you're not firing background requests on every admin's laptop all day.

Respect Nova's card request contract. The card re-fetches the same authorized endpoint the initial render used, so $request->filters and the range selector continue to apply. Verify this on your own metrics: apply a filter, wait for a poll, and confirm the numbers don't snap back to the unfiltered values.

Test the shape, not the timer. A feature test can hit the metric endpoint and assert the JSON payload includes the interval field the Vue component reads. Asserting on setInterval itself belongs in JS-land and isn't worth the setup for a package this thin.

Gotchas and Tips

Nova version drift is the real risk. Last release July 2023, zero dependents. Nova 4→5 changed card internals and asset compilation. Before committing, install in a branch, load a dashboard, and watch the network tab. If the card renders but never re-fetches, the Vue component is the thing that broke — not your metric.

Polling doesn't know about tab visibility unless the package implements it. Read resources/js/ and check for a visibilitychange handler. If there isn't one, a dashboard left open in a background tab keeps hammering your database indefinitely. That is the single most common way this class of package causes an incident. A wrapper component that pauses on document.hidden is a worthwhile afternoon.

Your authorization is fine; your query is not. Polling goes through Nova's authenticated card endpoint, so no new auth surface. But you've just multiplied your query volume by (dashboard sessions × polls per minute). Before enabling, run EXPLAIN on the underlying query and check it against an index — a 15s poll on a full table scan will be visible in your slow query log within a day.

Cache TTL vs. interval, again. If numbers look frozen mid-incident, the cache is the first suspect, not the polling. Temporarily set cacheFor to now()->addSeconds($interval) while debugging.

Manual refresh and polling can collide. Nova's built-in refresh button triggers the same fetch. If you see a loading spinner that never resolves, or two requests racing, it's usually because the package's timer fires while the user's manual refresh is in flight and the component doesn't guard against concurrent requests. Check for an in-flight flag in the Vue source.

Stagger your intervals. If every card returns 30, they synchronize and arrive as a thundering herd every 30 seconds. Randomize slightly — return 30 + random_int(0, 5); — or use distinct values per card.

Extension point for anything the package doesn't cover: override the card's jsonSerialize()/serializeForJs() in your house base class to pass whatever extra config the Vue component needs, and override pollingInterval() per metric to compute it from the metric's own cost (e.g., a different interval for the 7-day vs. 30-day range). Don't fork the package for this — subclass it.

Watch the vendored JS after any npm run prod. If you have a custom Nova tool that rebuilds assets, a stale compiled card component is a common cause of "it worked yesterday." Re-publish and recompile before you start debugging PHP.

Have an exit plan. The API surface here is tiny — a parent class plus one method. That's both the appeal and the risk: if the package dies, the migration cost is genuinely small. Keep the house base class in place, pin the version in composer.json, and note in the file why the package is there so the next developer doesn't have to rediscover it.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource