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

yaoko/nova-text-filter

View on GitHub
Deep Wiki
Context7

Getting Started

This is a single-purpose Nova filter package: it adds a free-text input (instead of the default select dropdown) to a resource or lens filter bar, so an admin can type a value and have it applied to the index query. With zero stars and zero dependents it is effectively an unreleased personal package — treat everything below as "read the source first, then trust it."

Install

composer require yaoko/nova-text-filter

Nova is a paid package, so your composer.json must already have the nova.laravel.com repository and credentials configured, otherwise this will fail with a 404 rather than anything useful.

Where to look first

  1. composer.json in the package — check the laravel/nova constraint. Nova 4/5 are Vue 3; Nova 1–3 are Vue 2. A stale compiled dist/js/filter.js against the wrong Nova major silently renders nothing.
  2. src/ — usually one or two classes: a base Filter subclass and a ServiceProvider that calls Nova::script(...).
  3. resources/js/ and dist/js/ — the Vue component that renders the input and registers itself against the filter's class name via Nova.booting.
  4. There is almost certainly no README worth reading. src/ is the documentation.

First use case

A "contains" search on a single column of a busy index — e.g. finding a user by partial email — where a dropdown of 40,000 options would be absurd. Create a filter that extends the package base class (or Nova's Filter while reusing the package's Vue component) and register it on the resource:

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

use Illuminate\Http\Request;
use Laravel\Nova\Filters\Filter;

class EmailContains extends Filter
{
    public $name = 'Email contains';

    public function apply(Request $request, $query, $value)
    {
        $value = trim((string) $value);

        if ($value === '') {
            return $query;
        }

        return $query->where('email', 'like', '%'.$value.'%');
    }

    public function options(Request $request)
    {
        return []; // text filter: no option list
    }
}
// app/Nova/User.php
public function filters(Request $request)
{
    return [new \App\Nova\Filters\EmailContains];
}

The options() returning [] is the whole trick — the base Filter renders a <select> when options exist, and the package's component renders a text input instead.

Implementation Patterns

The $value arrives as a string, always. Nova serialises the filter bar into filters[<key>] in the query string, and text inputs come back as strings (or null when never touched). Cast and trim before using it.

Multi-column search. This is the pattern you'll actually want most of the time — one input, several columns:

public function apply(Request $request, $query, $value)
{
    $value = trim((string) $value);

    return $query->when($value !== '', function ($q) use ($value) {
        $q->where(function ($q) use ($value) {
            $q->where('name', 'like', "%{$value}%")
              ->orWhere('email', 'like', "%{$value}%")
              ->orWhere('username', 'like', "%{$value}%");
        });
    });
}

The nested closure matters: without it the orWhere clauses escape any surrounding where (including Nova's own scoping and your indexQuery policy constraints) and you leak rows.

Lenses. Lenses accept the same filters() array, so the identical filter class works on both without changes:

// app/Nova/Lenses/RecentSignups.php
public function filters(Request $request)
{
    return [new \App\Nova\Filters\EmailContains];
}

Note that Nova's per-lens filter state is stored separately from the resource's — switching between the resource index and a lens does not carry the typed value across.

Pre-filling and placeholders. Use ->default('@example.com') when you want the input populated on first load, and ->withMeta(['placeholder' => 'Search email…']) if the package's component reads meta for the input attributes. If it doesn't, edit the Vue file directly — it's yours after install.

Combining with select filters. A text filter composes cleanly with dropdown filters in the same bar since each calls apply() in sequence on the same query builder. Keep each filter's apply() additive and never call ->where() on the root builder when you mean to constrain within the filter's own group.

Full-text instead of LIKE. On a table with more than a few hundred thousand rows, swap the LIKE for whatever your engine offers — whereFullText([...], $value) on MySQL/Postgres, or a trigram index — and keep the filter class as the single place that decision lives.

Gotchas and Tips

if ($value) is a bug. '0' is falsy in PHP. A text filter searching for the literal value 0 (an ID, a status code, a postal code) silently does nothing. Always compare explicitly: $value !== null && $value !== ''.

Escape LIKE wildcards. User input containing % or _ changes the semantics of the pattern — a lone % matches every row and looks like the filter is broken. Escape the value before wrapping it:

$escaped = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $value);

Case sensitivity is the database's decision, not Nova's. MySQL with utf8mb4_unicode_ci is case-insensitive for free; Postgres needs ILIKE or lower(col) LIKE lower(?); SQLite's LIKE is ASCII-case-insensitive only, so accented or non-Latin searches quietly miss. Decide once and encode it in the filter class.

Leading wildcards kill indexes. '%term%' cannot use a B-tree index; on large tables the filter will be the slowest thing in the admin panel. Consider a prefix match ('term%') or full-text search if the index is genuinely large.

Nothing renders? Check, in order: composer dump-autoload; the filter is actually returned from filters(); the package's service provider is auto-discovered (or added to config/app.php on older Laravel); dist/js/filter.js exists and is committed; and the Nova major matches the Vue version. The classic failure is a package compiled against Nova 3 being dropped into a Nova 4 app — the input never appears and there is no console error.

Don't change the class name or namespace casually. Nova derives the filter's storage key from key(), which by default tracks the class. Renaming EmailContains to EmailSearchFilter orphans any saved filter state in users' browsers and query-string bookmarks; override key() with a fixed string if you expect to refactor.

Never interpolate into whereRaw. It's tempting for concat_ws style multi-column search; use bindings. The filter value comes straight from a query string.

Debugging. The fastest loop is to logger($request->filters) at the top of apply(), then dd($query->toSql(), $query->getBindings()). If the SQL is right and the results are wrong, the problem is collation or an orWhere escaping its group — nine times out of ten it's the latter.

The real recommendation: vendor it. At zero stars and zero dependents, this package is a ~150-line snippet with an npm build attached. The compiled JS is an opaque blob you cannot patch, and any Nova upgrade will strand it. Copy src/ into app/Nova/Filters/, copy the Vue component into your app's resources/js/filters/, register it in your own resources/js/filter.js, run npm run prod, and drop the dependency. You get the same feature, a build you control, and no unmaintained package in your composer.json.

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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels