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 Filterable Metrics Laravel Package

nemrutco/nova-filterable-metrics

Nemrutco's Nova Filterable Metrics is a Laravel Nova package that lets you apply Nova filters directly to dashboard metric cards, so you can view metric results for specific segments. It makes Nova metrics more interactive and supports granular reporting.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm your Nova major before you composer require anything. The repository is archived, last released 2023-03-31, and v1.3.0 is the only release that added Nova 4 support. If you are on Nova 5 or Laravel 11+, assume the Composer constraints block you and budget for a fork rather than being surprised mid-sprint. Diff the package's composer.json against your Nova, Laravel, and PHP versions before writing a single metric.

composer require nemrutco/nova-filterable-metrics:^1.3

Pin it exactly (tagged commit or your own fork). Never let an archived package float on dev-main.

Where to look first, in order:

  1. src/ — the trait is the whole package. Read it end to end; it is a thin adapter over two Nova extension points. You will spend more time reading Nova's own Metric and Filter classes than this package's.
  2. The Vue component under resources/js — this is what renders the filter dropdowns on the card. If a filter UI silently disappears after a Nova upgrade, this file is why.
  3. The ServiceProvider — confirms whether anything is registered or published. It is minimal.

First use case — one card, one filter, one known number. Do not wire a whole dashboard first. Take a Trend metric whose current value you can reproduce by hand from the resources table:

namespace App\Nova\Metrics;

use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Metrics\Trend;
use Nemrutco\NovaFilterableMetrics\Filterable; // verify exact trait name in src/

class TicketsPerDay extends Trend
{
    use Filterable;

    public function calculate(NovaRequest $request)
    {
        return $this->countByDays($request, Ticket::class);
    }

    public function filters(NovaRequest $request): array
    {
        return [
            new \App\Nova\Filters\TicketStatus,
        ];
    }
}

Load the dashboard, confirm the dropdown renders, change the value, and confirm the number moves. Then verify the number against the same filter applied to the resource index. If those two disagree, stop — you have found the failure mode that matters (silent miscalculation), and everything downstream is built on a lie.

Ramp-up is genuinely hours, not days, for a Nova-familiar developer. The cost is not installation; it is ownership.

Implementation Patterns

Reuse, don't rebuild, your filters. The entire value proposition is that filters() accepts filter classes you already maintain for resource indexes. If you find yourself writing new Filter subclasses specifically for metrics, you are paying twice for one capability. If the team has no Nova filters, this package has nothing to attach to — close the ticket.

Standardise with a base metric class. Rather than sprinkling the trait across ad-hoc metric classes, define one base per metric type:

abstract class FilterableTrend extends Trend
{
    use Filterable;

    public function filters(NovaRequest $request): array
    {
        return [
            new DateRange,
            new Region,
        ];
    }
}

Then let individual cards override filters() only when they need a narrower set. This gives you a single place to add or retire a filter when the org changes, and it makes the fleet of cards reviewable at a glance.

Scope filters per card, not per dashboard. This is the package's defining behaviour and its main selling point: metric filters are independent from resource filters. A "revenue by region" card can be pinned to EMEA while the ticket card stays global, and neither is disturbed when a user selects a dashboard-level filter. Use that deliberately — it is how you build cards that answer the question the viewer actually asked without forcing everyone into the same slice.

Match the filter to the aggregation. The filters that translate cleanly are the boring ones: a where-clause on a column the metric already aggregates (status, region, team_id), and date ranges. Filters that build joins, rely on $request->resourceId, or reach into relationship constraint logic are the ones that produce plausible-but-wrong charts. When you add a filter to a card, add a line to your PR description showing the before/after number and how you verified it.

Wire a smoke test alongside. Hit the metric endpoint with the filter applied and assert on the response payload, so a Nova upgrade that breaks the contract fails CI rather than the ops dashboard:

$this->actingAs($admin)
    ->getJson('/nova-api/metrics/tickets-per-day?status=open')
    ->assertOk();

Assert on the actual value, not just a 200 — a filter that silently no-ops still returns 200 and a plausible chart.

Keep it on internal, read-only dashboards. Ops queues, support rollups, finance summaries. Nothing customer-facing, nothing contractual. That bound caps the blast radius of every failure mode in this package and lets you adopt it cheaply.

Gotchas and Tips

Silent miscalculation is the highest-severity failure, and it is invisible. A filter that applies but does not correctly constrain the aggregate produces a believable chart. Users trust wrong numbers faster than they trust right ones. For every filter you attach: reproduce the number with a raw query or a filtered resource index, and re-verify after any Nova upgrade. This is the single QA activity worth the time.

Filters aren't always resource-agnostic. A Filter written for a resource index may assume $request->resource() is populated or that a specific table alias is in play. On a metric endpoint that context is different or absent, so the filter can no-op or error. Test each filter class on a metric before assuming the whole set transfers.

cacheFor and filters can fight each other. If your metrics are cached, you must confirm the cache key includes filter state — otherwise users get the unfiltered number back and reasonably conclude the dropdown is broken. Verify explicitly with two different filter values in quick succession; if the second returns the first's number, disable caching on that card.

Date filters can double-apply against the card's own range. Nova's ranges() / defaultRange() and a date-based filter both constrain time. Confirm they intersect the way a user would read them rather than stacking into a narrower window than anyone intended.

Watch the query cost per click. Every filter change re-runs an aggregate. On high-cardinality tables, a user clicking through a dropdown generates repeated heavy scans. Confirm indexes on the columns your filters touch, and prefer filters whose values map to indexed columns. This is your query design, not a package scaling ceiling — but the package is what makes clicking cheap enough that people will do it a lot.

Authorization is yours, not the package's. A filtered metric still exposes an aggregate. Check canSee on both the metric and the filter classes so a slice of data doesn't leak to a role that can't see the underlying records. Filtering is not access control.

Debugging: look at both ends. In the browser, inspect the request to /nova-api/metrics/... and confirm the filter values are actually in the payload — a dropdown that renders but sends nothing is a frontend/Nova-version problem, not a query problem. Server-side, dump the SQL (->toSql() on the underlying builder, or Telescope) to see whether the constraint landed. That split tells you instantly whether you are debugging the filter or the aggregate.

Upgrade drift is the standing risk. Because upstream is archived, a Nova minor release can break the card's filter UI — an empty dropdown or a card error — with no fix path except your fork. Pin exactly, record the pin and a review date in your dependency register, name an owner, and treat adoption as "a small internal utility we now own," not "a dependency we took on." An unowned fork rots silently and resurfaces as a blocked Nova upgrade mid-migration.

Extension points are shallow by design, which is good. The surface is the trait plus the filters() method. If you need behaviour the package doesn't give you, override filters() in a base class or fork — do not fight the upstream, because there is no upstream to send a PR to.

Before any of this: check whether Nova now ships native metric filtering. If it does, the decision collapses to zero and you skip the fork entirely.

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.
frankkessler/guzzle-oauth2-middleware
delatbabel/site-config
delatbabel/applog
eusonlito/laravel-gettext
henzeb/housepedia-intelligence-laravel
aliqsm/laravel-authkit
itsjustvita/laravel-wp-migrator
uslanozan/laravel-cache-contract
adilazhari/laravel-trace
ulsyairil/ai-local-guardrails
dzwallet/laravel-sdk
glc36/maxloop-kernel
ikhbarnuridev/filament-mega-menu
mydnic/volet-chatbot
enadstack/laravel-localization
mydnic/volet-feature-board-filament-plugin
wmde/email-address
refinery29/test-util
dees040/laravel-api-responses
teamzac/geocoder