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

Nova4 Searchable Belongs To Filter Laravel Package

khalin/nova4-searchable-belongs-to-filter

A Laravel Nova 4 filter that adds a searchable belongs-to relationship filter to resource indexes. Users can type to find related records instead of scrolling long lists, making large datasets easier to filter and navigate.

View on GitHub
Deep Wiki
Context7

Getting Started

Install into a Nova 4 application only:

composer require khalin/nova4-searchable-belongs-to-filter

There is no config file or asset publishing step — the package ships a Vue component that Nova 4's build picks up automatically, and a PHP abstract filter class. Your first move after installing is to read the package's src/ directory: the single abstract class is the entirety of the public API, and the method names on it are what you override. Everything else is the front-end component.

Generate a filter with the Nova artisan command and then swap the base class:

php artisan nova:filter UserFilter
namespace App\Nova\Filters;

use App\Nova\User;
use Khalin\Nova4SearchableBelongsToFilter\Nova4SearchableBelongsToFilter;

class UserFilter extends Nova4SearchableBelongsToFilter
{
    public function filterName(): string      { return 'User'; }
    public function fieldName(): string       { return 'user_id'; }
    public function searchableResource(): string { return User::class; }
}

Register it on the resource's filters() method. Verify the exact method signatures against the abstract class before you commit — this is a small, single-author package last released in April 2022, and the contract is not documented.

The canonical first use case is exactly the one Nova's built-in BelongsTo filter handles badly: a posts.user_id relationship where the users table has tens of thousands of rows and a <select> dropdown is unusable. Swap the built-in filter for this one and you get a type-ahead search box backed by Nova's own search endpoint.

Implementation Patterns

One filter class per relationship. Do not try to parameterise a single filter class across two foreign keys. Generate a distinct class per belongsTo, each returning its own fieldName() and searchableResource(). This keeps filterName() meaningful in the UI and makes per-field authorization trivial.

Delegate search to the Nova resource, not to the model. The component resolves options through the resource class you return from searchableResource(), which means it inherits that resource's searchable() columns, title attribute, and — importantly — its authorizedToView() check. If a user cannot view User resources, they generally will not see them in the dropdown either. Use this deliberately: return a restricted resource subclass if a filter should expose a narrower set of options than the full index.

Pair with a global filter for cross-resource reuse. Because the filter only needs a foreign key name, the same class can be attached to Post, Comment, and Audit resources as a global filter via Nova::globalFilters(). Local filters are the safer default; reach for global only when the foreign key genuinely exists on every resource that inherits it.

Compose with other filters normally. The filter's apply() narrows the query with a where on the foreign key, so it stacks with date filters, status filters, and Nova search in the usual AND fashion. There is no special ordering requirement.

Use it as the primary UX for polymorphic-adjacent cases too. If you have a commentable_id that is only ever one type, the package works as ordinary belongsTo filtering without a polymorphic type check — add that check yourself in an overridden apply() if the column is shared.

Gotchas and Tips

Nova 4 only. The package name is literal. Do not install it on a Nova 3 project expecting a graceful fallback, and treat Nova 5 as untested — the repository has had no release since 2022 and four stars, so assume no upstream maintenance. Vendor the class or fork it before a Nova major upgrade rather than waiting for a fix.

The stored value is a raw ID. Filters serialise their value into the query string, so ?user_filter=42 is bookmarkable and shareable. That also means it is user-editable: never trust it as an authorization boundary. The query narrowing must be backed by a policy on the underlying resource, not by the fact that the ID appeared in a search dropdown.

Null and empty handling. Clear the filter and Nova sends null or an empty string depending on version. Verify your apply() guards against it — an unguarded where('user_id', '') silently returns zero rows and looks like a bug in the filter rather than in the query.

Search returns nothing for a valid model. Almost always a missing searchable() definition on the target resource, or a model without a title attribute and no $title fallback. Test with User::searchable() in tinker against the resource's column list. This is the single most common failure mode and it has nothing to do with the filter.

Soft deletes and withoutGlobalScopes. The dropdown searches with the resource's default query, so trashed records are invisible and any global scope you added to the model applies. If a filter must surface archived parents, that requires overriding the search path — check whether the abstract class exposes a hook for the query builder before assuming you can inject one.

Debug the backend, not the Vue side. When the dropdown is empty in the browser, look at the network tab for the Nova search request, then run the equivalent query in tinker. If the endpoint returns results but the dropdown does not, the mismatch is in fieldName() — usually a foreign key that does not match the column Nova is hydrating.

Extension points are the parent methods. The abstract class almost certainly leaves apply() and the option-resolution method open. Override apply() to add a soft-delete clause, a tenant scope, or a compound where; override the resolution method if you need to search a joined column rather than the resource's default columns. Read the class source first — it is short, and this is the only reliable documentation you will get.

Watch index size on the parent table. The search itself hits the parent table via Nova's search, which uses LIKE on the searchable columns by default. On large tables add a database index or a full-text index on the searchable columns; the filter does not change that cost, it only makes it reachable from a filter panel where it will be hit far more often than before.

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