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

harrald/nova-combobox-filter

A Laravel Nova filter that adds a searchable combobox dropdown for filtering resource listings, letting users quickly pick from related records instead of scrolling long select menus.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm Nova compatibility before anything else. This package is a Nova-only UI filter — it does nothing outside Nova. Nova 3 (Vue 2 + Mix), Nova 4, and Nova 5 (Vue 3 + Vite) differ materially in asset registration and component conventions, and a mismatch usually fails silently: the combobox renders, the user picks a value, and nothing is sent to the server. Open vendor/harrald/nova-combobox-filter/composer.json, read the require block against your Nova major, and treat a loose constraint (*) as a prompt to test on a branch first.

Install and publish.

composer require harrald/nova-combobox-filter
php artisan nova:publish

If the package ships uncompiled resources/js, build it once (or in CI — see Gotchas):

cd vendor/harrald/nova-combobox-filter && npm install && npm run build

Read three things, in this order.

  1. README.md — the exact base class name. It starts Harrald\NovaComboboxFilter\...; do not guess it.
  2. src/ — the abstract filter. You care about options() (what values are offered) and apply() (how the query is constrained). Everything else is plumbing.
  3. resources/js/ — skim the Vue component. The decisive question is whether it serialises all options into the Nova page payload or supports server-side search. That answer determines whether the filter is usable on a given resource at all.

Minimal first filter. Take an existing select filter that has outgrown itself — "filter orders by status", "filter tickets by tag", "filter users by team" — and swap the base class:

// app/Nova/Filters/StatusFilter.php
namespace App\Nova\Filters;

use Harrald\NovaComboboxFilter\ComboboxFilter; // confirm exact class in README

class StatusFilter extends ComboboxFilter
{
    public $name = 'Status';

    public function options(Request $request)
    {
        return [
            'draft'     => 'Draft',
            'published' => 'Published',
            'archived'  => 'Archived',
        ];
    }

    public function apply(Request $request, $query, $value)
    {
        return $query->whereIn('status', (array) $value);
    }
}

Register it the normal Nova way — nothing special:

public function filters(Request $request)
{
    return [new \App\Nova\Filters\StatusFilter];
}

That is the whole adoption cost. The rest of this assessment is about keeping it correct through upgrades.

Implementation Patterns

Put a seam between your app and the vendor class. Don't extend Harrald\NovaComboboxFilter\ComboboxFilter from twenty resources. Extend an internal App\Nova\Filters\ComboboxFilter that extends the vendor one. If you later migrate to Nova's native SelectFilter::searchable()->multiple() or write your own component, it is a single-file change.

// app/Nova/Filters/ComboboxFilter.php
namespace App\Nova\Filters;

use Harrald\NovaComboboxFilter\ComboboxFilter as BaseComboboxFilter;

abstract class ComboboxFilter extends BaseComboboxFilter
{
    protected function values($value): array
    {
        return array_values(array_filter((array) $value, fn ($v) => $v !== null && $v !== ''));
    }
}

Normalise inputs in apply() rather than trusting the front end. The submitted value may arrive as a scalar, an array, or an empty array depending on selection count and Nova version. Handle all three explicitly:

public function apply(Request $request, $query, $value)
{
    $values = $this->values($value);

    if (empty($values)) {
        return $query->whereRaw('1 = 0'); // "empty selection yields nothing"
    }

    return $query->whereIn('status', $values);
}

Pick whereRaw('1 = 0') (empty selection = zero rows) or return $query (empty selection = no filter), document which in the class docblock, and stick to it across every filter. Users will assume one behaviour and be surprised by the other.

Source options from a query, but cap the result. The combobox wants a keyed array, so pluck('name', 'id')->all() maps naturally — just don't serialise thousands of rows into the Nova index payload:

public function options(Request $request)
{
    return \App\Models\Team::query()
        ->orderBy('name')
        ->limit(200)
        ->pluck('name', 'id')
        ->all();
}

Collapse redundant filters. If users toggle three single-value filters on the same column today, replace them with one multi-select combobox. That consolidation is the actual payoff of the package — "Draft" and "Published" in one control, one chip row, one query parameter.

Keep apply() to a single constraint. It runs on every index load. Composing it as one whereIn keeps pagination and eager loading predictable. Joins and subqueries belong in the resource's indexQuery(), not here.

Pin the contract with a test. Twelve stars, zero dependents, single author — you own maintenance. A feature test that asserts the compiled SQL is the cheapest insurance against the "filter stopped constraining results after a Nova upgrade" failure mode:

public function test_status_filter_applies_where_in()
{
    $query = Order::query();
    (new StatusFilter)->apply(request(), $query, ['draft', 'published']);
    $this->assertStringContainsString('"status" in', $query->toSql());
}

public function test_empty_selection_semantics()
{
    $query = Order::query();
    (new StatusFilter)->apply(request(), $query, []);
    $this->assertSame('select * from "orders" where 1 = 0', $query->toSql());
}

Pin the version. Commit the exact version in composer.lock, and if nova:publish emits an asset you depend on, commit that published asset too. A caret range on a single-author package with no CI is optimism, not a strategy.

Gotchas and Tips

Silent correctness failures are the dangerous mode. The bad outcome isn't a crash — it's a filter that renders, accepts a selection, and then quietly stops constraining rows after a Nova upgrade. Users see unfiltered data they believe is filtered, and there's no error anywhere. Cover it with the SQL tests above and a staging smoke check after every Nova bump.

Asset publishing is the classic dev/prod split. If the package ships uncompiled JS, the failure looks like this: works locally because Vite/Mix is running, dead or unstyled dropdown in production because the asset was never built. Decide up front — either publish-and-commit the compiled asset or build it in CI. Do not leave it to whoever deploys next.

Option cardinality is the scaling ceiling. If the component serialises every option into the Nova response, every page load of that index pays for it. Don't attach this filter to genuinely high-cardinality columns (all users, all orders, all tenants) without confirming in resources/js/ whether search is server-side. Cap with ->limit() and treat low hundreds as the practical target.

This is not async relationship search. "Search across a million users and pick several" is a different problem — server-backed endpoint, dependsOn, or a custom component. This package filters enumerable option sets. If you need anything more, that's a build, not a require.

Check native parity first. Nova 4+ ships SelectFilter with ->searchable() and ->multiple() in some configurations. If the native filter already covers your need, delete the dependency — that's the highest-value finding of this whole evaluation and worth thirty minutes before you write any subclass.

Resolve the docs contradiction before promising it. The package description advertises "supports selecting multiple items" while its feature list mentions single-value selection. Read apply() and the Vue component and confirm with your own eyes whether multi-select actually works on your Nova version. Don't put "multi-select filtering" on a sprint ticket until you've seen it in the browser.

URL state and saved bookmarks. Filter values live in the query string. If you migrate from a native filter that sent ?status[]=draft to one that sends a scalar — or the reverse — existing bookmarked index URLs desync. Either migrate them or accept the one-time break knowingly.

Debugging checklist when the filter "does nothing" — each step isolates a layer:

  1. Is the filter actually returned by the resource's filters() method?
  2. In the network tab, does the index request URL carry the filter parameter after you select a value?
  3. In the browser console, does the Vue component mount cleanly? A blank or unstyled widget means a toolchain mismatch, not a logic bug.
  4. Does apply() run? Drop a temporary logger() in it — silence means the front end isn't emitting.
  5. What type is $value when it arrives? Log it and normalise with (array).

Working down that order turns "it's broken" into a specific fix at one layer: registration → component → request → PHP.

Assume you own the code. Small source tree, no dependents. Read src/ and resources/js/ — they're short — keep the wrapper class, and budget half a day to re-implement in app/Nova/Filters if the package goes stale or the Nova version drifts too far. The build-it-yourself alternative is roughly 1–2 engineer-days, and choosing it is a legitimate outcome if the package doesn't fit cleanly. Don't fight 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.
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
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect