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 Dependent Filter Laravel Package

resham/nova-dependent-filter

A Laravel Nova package that adds dependent filters, letting one filter's options update based on another's selected value. Build cascading filter logic in Nova resource index views with minimal setup and no custom JavaScript required.

View on GitHub
Deep Wiki
Context7

Getting Started

Install order matters. This package has no standalone mode — composer require resham/nova-dependent-filter resolves against laravel/nova, so if Nova isn't already installed and locked you'll get a confusing dependency error that never says "install Nova first."

composer require resham/nova-dependent-filter
php artisan nova:publish   # if the package ships assets requiring publish

Read composer.json before you trust it. Three lines decide whether you can proceed: the laravel/nova constraint (4.x vs 5.x, Vue 2 vs Vue 3), the PHP floor, and whether it ships precompiled JS or raw .vue files needing a consumer build. With 3 stars and zero dependents, expect a single Nova-major pin. If your Nova version falls outside that constraint, stop — the frontend component almost certainly won't mount, and it will fail silently rather than loudly.

Where to look first, in order:

  1. vendor/resham/nova-dependent-filter/src/ — the base Filter subclass that owns the cascade logic. This is the source of truth; the README is thin. Confirm the exact method names (something like parent() and an options() that receives active filter values) here before writing anything.
  2. vendor/resham/nova-dependent-filter/resources/js/ — the Vue component. You need to know whether it watches a sibling prop, polls, or hits an endpoint; that determines how you debug it later.
  3. Your resource's filters(Request $request) method — where the wiring lives.

First use case: country → city, on a non-critical resource. Extend the package's base class (not Nova's Filter), declare the parent, and scope options by the current parent value.

// app/Nova/Filters/CountryFilter.php
class CountryFilter extends DependentFilter
{
    public function apply(Request $request, $query, $value) { /* ... */ }
    public function options(Request $request) { /* all countries */ }
}

// app/Nova/Filters/CityFilter.php
class CityFilter extends DependentFilter
{
    public function parent(): string { return CountryFilter::class; }

    public function options(Request $request, array $activeFilters)
    {
        $countryId = $activeFilters[CountryFilter::class] ?? null;
        if (blank($countryId)) {
            return [];                       // empty, never "all"
        }
        return City::where('country_id', $countryId)->orderBy('name')->pluck('name', 'id');
    }
}

Register parent first, always:

public function filters(Request $request)
{
    return [new CountryFilter, new CityFilter];
}

Pick a country on the index view; the city list should narrow. If it renders but never updates, you're on the wrong base class — see Gotchas.

Implementation Patterns

Wrap, don't inherit directly. Create App\Nova\Filters\DependentSelect extending the package base, and have every dependent filter extend that. It confines the third-party dependency to one file, makes swapping to a hand-rolled implementation a single change, and gives you a place for shared query logic. Do this on day one — the bus factor here is one maintainer.

Three-level cascades: read only your immediate ancestor. Category → subcategory → SKU. Each level resolves from its direct parent, not two levels up. A broken middle level then fails locally instead of corrupting everything downstream.

Guard clause on empty parent — the most important line in any options().

if (blank($parentValue)) {
    return [];   // NOT: select all, NOT: unbounded pluck
}

Returning "everything" when the parent is unset is the single most common way these filters become a cold-start performance incident on a large admin index — the first page load has no parent selection, and an unbounded pluck() hits live data immediately.

Cache short-lived option lists. If the frontend refetches on every parent change, wrap the lookup:

return Cache::remember("city-opts:{$countryId}", 60, fn () =>
    City::where('country_id', $countryId)->orderBy('name')->pluck('name', 'id')
);

Key by parent id (and tenant, if multi-tenant). One-minute TTL is fine for admin tooling and eliminates repeated SELECT DISTINCT calls during a filtering session.

Scope by policy, not just by parent. Dependent filters return real IDs and labels. If options() doesn't go through the same authorization scoping as the index query, a user who can see the child filter can enumerate records they cannot view. Run the options query through the same builder or policy check the resource uses.

Smoke-test the render, not just the logic. A feature test hitting the Nova resource index endpoint as an authenticated user, asserting 200, catches silent filter-mount breakage — Nova changes between majors break rendering more often than filter logic.

Prefer and verify URL-driven state. Nova filters already live in the query string; the package inherits that. Confirm you can copy a cascaded URL into a new tab and get the same view — that's the workflow your support team will actually rely on, and it doubles as your primary debugging tool.

One abstract per resource family. Wiring the same cascade across several Nova resources? Put shared logic in a single abstract with an abstract parent() and a protected options builder. One place to fix when Nova breaks it.

Gotchas and Tips

Register parent before dependent, every time. The Vue component subscribes to the parent's value; if the parent isn't mounted first, the child renders but never updates. This is the #1 cause of "installed fine, does nothing."

Extend the package base class, not Nova's Filter. Sibling-value resolution and the frontend contract live in the package base. Extending Laravel\Nova\Filters\Filter gives you a filter that looks correct in the UI and silently fails to cascade — the hardest class of bug to notice.

Nova assets are published per deploy, not per request. After composer update or a Nova upgrade, run nova:publish in the deploy pipeline (with --force in CI). Stale published JS versus source is a classic silent failure: filters render, options never refresh.

Orphaned filter state. Clearing the parent does not automatically clear the child's query-string value, producing an empty result set that looks like a bug. Either treat a stale child with no parent as a no-op in apply(), or document the reset path for admins.

Cross-tab collisions. Filters are URL state; two tabs on the same resource stomp each other's cascade. Reproduce with a copied URL before code-diving — it's almost never a package bug.

Nova major upgrades are the main maintenance risk. Nova's frontend internals churn between majors and this package rides on that undocumented surface. Budget roughly a day per upgrade to patch or replace, and fork under your org account before you need to patch.

Debugging checklist when options stop refreshing, in order:

  1. Is the parent filter registered first in filters()?
  2. Did you extend the package base class rather than Nova's Filter?
  3. Did nova:publish run for the current deploy?
  4. DevTools → Network → is a request fired on parent change? No → the Vue component isn't watching the right sibling (registration order or base class). Yes → 500 means server-side options(); stale payload means published assets.
  5. Copy the URL into a fresh tab — does it reproduce? Then it's query state, not the frontend.

Extension points worth knowing: the base class's options(), the parent() declaration, and any hook for "when the parent changes, do X" (cache invalidation, logging). Beyond those three, treat everything as private — pin the version, don't rely on incidental behavior.

When to stop and hand-roll. Confined to one screen, or you need a filter shape the package doesn't handle (date ranges, belongsTo lists with custom presentation, policy-nuanced option sets)? A custom Vue Nova filter is comparable effort with full ownership. The package pays off only when the cascade repeats across multiple Nova resources and a single-major Nova pin is acceptable — and it should never become load-bearing for revenue-, support-, or finance-critical workflows.

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