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

philperusse/nova-column-filter

philperusse/nova-column-filter adds configurable column filters to Laravel Nova resource index tables, letting users narrow records by specific fields directly from the Nova UI for faster, more focused browsing.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm it installs before you write any code. The last release is 2019-02-25, which puts the package in the Nova 1.x/2.x, Laravel 5.7–6, PHP 7.2–7.4 window. The cheapest possible answer to "is this viable for us" is a constraint check:

composer require philperusse/nova-column-filter --dry-run

If Composer resolves against your pinned laravel/nova and PHP version, install for real. If it conflicts, stop — port the pattern instead (see below). Do not spend time reading the source before you know this.

Where to look first, in order:

  1. src/ — the ColumnFilter class. This is the whole package: a Nova Filter subclass with a column allowlist and an apply() that builds a WHERE. Read it end to end; it is short enough to hold in your head.
  2. composer.json — the exact Nova/Illuminate/PHP constraints. This tells you what era it was built for.
  3. resources/js/ and any compiled dist/ asset — determines whether you need to publish assets and whether the frontend half will render under your Nova build.
  4. README.md — for the exact make() / columns() signature, since it is the only authoritative source for the fluent API.

First use case — a status/owner dropdown on an internal resource. Register the filter in the resource's filters() method with a static, developer-defined column list:

use Philperusse\NovaColumnFilter\ColumnFilter;

public function filters(Request $request)
{
    return [
        ColumnFilter::make('Columns')
            ->columns([
                'status',
                'owner_id',
                'is_archived',
            ]),
    ];
}

The shape of the API above is the pattern to expect — verify the exact method names against src/ before committing, because the README is the only place the current signature is documented. Then load the resource index in the browser and confirm the filter appears in the Nova filter panel before testing that it actually constrains results.

Do not reach for the package on a customer-facing surface. It is an admin-panel convenience and belongs only there.

Implementation Patterns

Treat the filter as a per-resource allowlist, not a global switch. The value of the package is that you decide, per resource, which columns a user may query. Keep that list short, static, and indexed:

ColumnFilter::make('Columns')
    ->columns([
        'status',        // indexed enum
        'owner_id',      // indexed FK
        'created_at',    // indexed timestamp
    ])

Never derive the column list from request input, a config table, or Schema::getColumnListing() at runtime. The whole safety story of this package rests on the column and operator sets being developer-defined constants. The moment a column name can be influenced by a user, you have SQL injection and cross-tenant disclosure inside the admin panel — a hard stop, not a fork-and-fix.

Two filters, two jobs. If a resource genuinely needs arbitrary querying and a fast answer to a recurring question, ship both: the column filter for the power user, and a normal hand-written Nova filter for the common case:

public function filters(Request $request)
{
    return [
        new Filters\ActiveOnly,                       // curated, index-aware, one click
        ColumnFilter::make('Columns')->columns([...]), // ad-hoc, for the power user
    ];
}

This keeps the common path one click and the uncommon path available, without the query builder becoming the default mental model.

Subclass it rather than configuring it everywhere. If several resources want the same allowlist and the same operator behaviour, put a thin base class in app/Nova/Filters:

namespace App\Nova\Filters;

use Philperusse\NovaColumnFilter\ColumnFilter;

abstract class SafeColumnFilter extends ColumnFilter
{
    abstract protected function allowedColumns(): array;

    public function columns(): array
    {
        return $this->allowedColumns();
    }
}

This is also the seam you use when you eventually port: swap the parent class for your own implementation and every resource keeps working. Keeping the extends in exactly one place is what makes the exit cheap.

Wire it to Nova lenses, not just the index. A lens is already a curated query. Adding a column filter on top of a lens is where day-to-day value shows up — the lens fixes the scope, the column filter narrows within it. Verify the generated predicate composes correctly with the lens's own constraints rather than replacing them.

Integration tips that pay off repeatedly:

  • Put the filter in a resource whose table is small enough that an accidental sequential scan is embarrassing but not an outage. Prove the behaviour there first.
  • Add a test that asserts the generated SQL respects global scopes, soft deletes, and your tenant boundary. This is an explicit test, not a code read — hand-built predicates are the classic way a filter leaks rows across tenants.
  • Log or dd() the query once during rollout with ->toSql() and getBindings() to confirm bindings are used and the column is not interpolated into the SQL string.

Gotchas and Tips

Silent non-rendering is the failure mode to plan for. A Nova filter that fails to register or whose asset fails to publish typically produces no error — it just is not there. Users conclude the capability does not exist and stop asking. Test rendering explicitly in a real browser against your exact Nova build; do not assume a green PHP test means the dropdown appears.

The frontend half and the PHP half fail independently. The apply() signature has been broadly stable across Nova 2–4, but asset registration and filter metadata have not. Nova 4's Vite-era build tooling is the likeliest break point for a 2019 compiled asset. Budget the asset spike separately from the query spike, and if you must check assets require a publish step, confirm it survives your deploy pipeline (nova:publish in the release script, not on someone's laptop).

Never let a LIKE '%…%' reach a large table. Generic column querying invites unindexed WHERE clauses, LIKE scans, and correlated subqueries. If a column is high-cardinality free text, leave it out of the allowlist or add a generated/fulltext index first. Measure on your largest resource table before rollout — if query time regresses, the fix is a shorter allowlist, not a faster server.

Global scopes, soft deletes, and tenancy need a test, not a review. The base Nova index query usually carries these, but predicates appended inside apply() can bypass them depending on how the query is built. Write the test that asserts a soft-deleted or other-tenant row does not appear in results when a column filter is applied. This is the one place where a bug is a data exposure rather than a UX annoyance.

Pin your exit before you start. With no release since 2019, no dependents, and a single maintainer, every future Nova or PHP major becomes unplanned work on your team. Two options:

  • Port the pattern into app/Nova/Filters — roughly 150 lines of PHP plus a small UI — and keep the MIT attribution in a header comment. This is the recommended path, and it is under a day for a developer fluent in Nova and Eloquent.
  • Fork and vendor into an internal namespace with a named owner and a review trigger at your next Nova major. MIT makes the licensing easy; accountability is the real decision.

Do not add it to composer.json as a routine dependency unless a team is prepared to own the fork. Adopting a 2019-era, zero-dependent package is a decision to own unmaintained code — make that a named line item with an owner, not an assumption.

Extension points are thin, which is the point. The class exposes the column allowlist and apply(). Anything beyond that — custom operators, per-column input types, saved views, compound filters, exports — is not a package feature and should not be forced into one. If the roadmap genuinely wants slicing and saved reports, that belongs in a purpose-built surface (a BI tool or an internal dashboard), not in Nova filters. Nova is the admin surface; keep it there.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
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
cgv/localized-resource