sloveniangooner/searchable-select
A Blade-ready Laravel package that adds a fast, searchable select dropdown to forms. It supports filtering large option lists, AJAX/server-side search, and a clean, customizable UI for quicker, more accurate single-value selection.
1. Confirm compatibility before anything else. This is a single-maintainer Nova field with no dependents, so the first step is reading composer.json in the repo for the laravel/nova constraint. Nova 4+ moved to Vue 3 and the laravel-nova mixin/FormField contract changed; a field built for Nova 3 will install fine and then render as a blank row with no error in the UI.
composer require sloveniangooner/searchable-select
php artisan nova:publish # only if the package registers assets via a service provider
Where to look first, in this order:
| Path | Why |
|---|---|
composer.json |
Nova version constraint + whether dist/ is committed |
src/*.php |
The field class — the real public API (method names vary wildly between these small packages) |
src/*ServiceProvider.php |
Nova::script() call and the path it points at |
resources/js/field.js / dist/js/field.js |
Which upstream component it wraps (vue-multiselect, v-select, custom) |
README.md |
Usage snippet — take it as a starting point, verify against src/ |
First use case — the one this package actually earns its keep on: a plain Select field backed by a non-relationship options list large enough (say 300+ rows) that Nova's native <select> becomes unusable to scroll. If you're selecting a related model, stop here and use Nova's built-in BelongsTo::make('Category')->searchable() instead — it already does server-side search and won't rot.
use SlovenianGooner\SearchableSelect\SearchableSelect;
public function fields(NovaRequest $request)
{
return [
SearchableSelect::make('Category', 'category_id')
->options(Category::orderBy('name')->pluck('name', 'id')->all())
->rules('required', 'exists:categories,id'),
];
}
Verify the method names (options, searchable, placeholder) against src/ before committing — treat the snippet above as a sketch, not documentation.
Static vs. dynamic options. Options are serialized into the field's JSON payload at resource-fetch time, so any pluck() inside fields() runs on every index, detail, and form request for that resource. Keep the list small or wrap it:
->options(Cache::rememberForever('category-options', fn () =>
Category::orderBy('name')->pluck('name', 'id')->all()
))
If the option set is genuinely large, ->onlyOnForms() so it never touches the index query.
Storing IDs, displaying labels. Two patterns, pick one and be consistent:
// A: keyed array — the key is stored, the value is displayed
SearchableSelect::make('Category', 'category_id')
->options(Category::pluck('name', 'id')->all())
// B: resolve/display hooks when the option source isn't a plain map
SearchableSelect::make('Category', 'category_id')
->options($this->categoryOptions())
->resolveUsing(fn ($value) => $value)
->displayUsing(fn ($value) => Category::find($value)?->name)
Pattern A is the one these fields are usually designed around and the one least likely to surprise you.
Dependent selects. Nova's dependsOn re-runs the resource's fields() callback via a /nova-api/.../creation-fields or .../update-fields round trip, so a closure over $request works:
SearchableSelect::make('City', 'city_id')
->options(fn () => City::where('country_id', $request->country_id)
->pluck('name', 'id')->all()),
This costs a request per change — acceptable for a form, painful if you chain three.
Validation is on you. Unlike BelongsTo, a searchable select has no opinion about the underlying table. Always add ->rules('required', 'exists:categories,id') or you'll get a clean-looking dropdown that happily persists garbage via a crafted request.
Index column formatting. These fields often render raw IDs on the index. Add ->displayUsing(...) or ->hideFromIndex() explicitly rather than assuming.
Unknown custom element <searchable-select> or a 404 on /nova-api/.... Fixes: check dist/js/field.js exists in vendor/sloveniangooner/searchable-select/; if not, cd into the vendor dir and npm install && npm run prod. Clear storage/framework/cache and re-run php artisan nova:publish.0 vs "0". PHP array keys become JS object keys become strings. If any option ID is 0 or the value is null, loose comparison in the wrapped component can leave the field looking empty while the DB has a value. Use non-zero integer keys.null. Clearing the field commonly persists '', which blows up a nullable integer column (or FK constraint). Add ->nullable() and confirm what the component emits by inspecting the PUT payload.BelongsTo::searchable() or a custom field.saved model observer is the cheap fix.nova-components/ and swapping the Vue component is usually faster than fighting the package.Select::make()->options()->searchable() in Nova 4+) as the exit ramp.How can I help you explore Laravel packages today?