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

Dependent Filter Laravel Package

awesome-nova/dependent-filter

Nova dependent filter lets one filter's options update based on another's selection, keeping resource filters interactive and context-aware. It supports chained selects, custom queries, and flexible configuration for building responsive admin filtering.

View on GitHub
Deep Wiki
Context7

Getting Started

Before installing, check compatibility. This package's last release was February 2019, which puts it in the Nova 1.x/2.x era (Laravel 5.7–6.x, Vue 2). On a modern Nova (4/5) install, expect the JS side to be the first thing to break — the PHP base class may still work, but the client-side filter-refresh logic almost certainly will not. Pin it in a throwaway branch and run it against your Nova version before committing to it.

Install and inspect.

composer require awesome-nova/dependent-filter

Then open vendor/awesome-nova/dependent-filter/src/ immediately — this is the whole package. Read, in this order:

  1. The base filter class (usually DependentFilter) — note whether it extends Laravel\Nova\Filters\Filter and what abstract methods it adds (options(), apply(), and whatever dependency hook it exposes, e.g. dependsOn() or a $dependsOn property).
  2. The Vue component in resources/js/ — this is what listens for a parent filter change and re-fetches options.
  3. README.md — the API is small enough that the README plus the base class is the entire surface area.

If the package ships a custom component, publish Nova's assets (php artisan nova:publish) or the package's own vendor:publish tag before testing, or the dependent filter will render but never update.

First use case — a two-level cascading filter. The canonical scenario is Country → State on a resource's filters() method:

public function filters(Request $request)
{
    return [
        new CountryFilter,
        new StateFilter, // extends the package's DependentFilter
    ];
}

Register the parent as a normal Nova filter, make the child extend the package base class, implement apply($request, $value) as usual, and implement options() so it reads the currently-selected parent value and returns only the matching rows. Verify the cascade works in the browser before wiring it into lenses or actions.

Implementation Patterns

Parent stays vanilla, child becomes dependent. Don't convert every filter. Only the filter whose options must react to another selection needs the base class; the filter it listens to can be a plain Laravel\Nova\Filters\Filter. This keeps the blast radius small if you later have to drop or fork the package.

Read the parent's value from options(), not apply(). apply() only runs on the final query build and receives just its own value. The dependent behavior lives entirely in options(Request $request):

public function options(Request $request)
{
    $countryId = $this->parentValue($request, CountryFilter::class);
    // ^ exact helper differs by package version; in older builds it's
    //   reading the decoded $request->input('filters') array yourself.

    if (! $countryId) {
        return []; // or all states — decide and document it
    }

    return State::where('country_id', $countryId)
        ->orderBy('name')
        ->pluck('name', 'id')
        ->all();
}

Nova transmits active filters as a base64-encoded JSON blob in the filters query parameter, so "just read $request->country" will not work. If the package doesn't already decode it for you, decode json_decode(base64_decode($request->input('filters')), true) and match on the parent filter's key().

Give every filter a stable key(). Cascades break silently when two filters share a default key derived from the class name, or when a filter is re-instantiated with a different key on a lens. Define public function key() { return 'state_filter'; } on both parent and child so the JS lookup and the URL decoding stay in sync.

Validate the dependent value server-side. The child's apply() receives whatever the client sent, which may no longer match the parent (stale URL, hand-edited query string, shared link). Guard it:

public function apply(Request $request, $value)
{
    $countryId = /* decoded parent */;

    if (State::whereKey($value)->where('country_id', $countryId)->doesntExist()) {
        return $this->builder; // ignore an out-of-scope selection
    }

    return $this->builder->where('state_id', $value);
}

Treat the cascade as UX sugar, not as an authorization boundary.

Chain three levels deliberately. Country → State → City works, but each level re-fetches on every change above it. Keep the chain shallow (two, occasionally three) and make the leaf options a plain cached list rather than another reactive query. If you need deeper nesting, a single custom filter with a custom Vue component is usually less fragile than stacking three of these.

Reuse in lenses. Lenses accept the same filters() array, so a cascade built for the index can generally be passed to a lens unchanged — a good way to validate that options() is request-scoped and not relying on resource state.

Gotchas and Tips

The filters query string is base64 JSON — this is the #1 reason dependent filters "don't see" the parent. Log the decoded array inside options() first thing. Nine times out of ten the parent is present under a different key than you assumed, or the value is the filter's option label rather than its stored key.

No maintenance since 2019. There are no fixes coming for Nova's Vue 3 migration, changed asset publishing, or Laravel 10/11. Plan for one of: (a) use it only on a legacy Nova, (b) copy the base class + component into app/Nova and own it, or (c) reimplement the ~100 lines yourself with a small custom Vue filter. Given the package is small, forking into your codebase is usually cheaper than fighting composer constraints.

Empty parent behavior is a design decision, not a default. Most implementations return all options when no parent is selected; some return none. Pick one, document it in the filter, and make sure the "no parent selected" state doesn't accidentally widen a query in apply().

Cache the option query. options() runs on every filter-panel render and on every parent change. A pluck() against a large states table will feel sluggish. Wrap it in Cache::remember("states:{$countryId}", 3600, ...) and bust on write, or at minimum add an index on the FK you're filtering by.

Asset caching will bite you during development. After touching any Vue file in vendor/, run php artisan nova:publish (or the package's publish tag) and hard-refresh. A stale compiled component is indistinguishable from "the cascade doesn't work" and will cost you an hour.

Debug order when a dependent filter won't update:

  1. Decode the filters query param and confirm the parent value is there.
  2. Confirm the child component is actually registered (check the rendered HTML for the component name).
  3. Check the browser console — old Vue 2 components throw on modern Nova.
  4. Confirm the child's key() matches what the JS is listening for.

Extension points are shallow. The only reliable hooks are options(), apply(), key(), and whatever meta you attach via ->withMeta(). Don't expect events, middleware, or a config file — there is effectively no config surface, which is fine for something this small but means customization = forking.

Watch for double-apply. If the package injects any withMeta defaults and your apply() also reads the parent filter from the request, you can end up filtering by parent and child simultaneously. Check the generated SQL with ->toSql() on the filter builder once and confirm it matches your intent.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
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