hubertnnn/laravel-nova-field-dynamic-select
A Laravel Nova field for dynamic select inputs, letting dropdown options update based on other form fields so dependent selects stay accurate without custom JavaScript. Useful for cascading choices, filtered options, and linked form fields in Nova resources.
Install and confirm the wiring
composer require hubertnnn/laravel-nova-field-dynamic-select
The package ships a field class plus compiled JS/CSS assets. Auto-discovery should register its service provider; if the field renders without behaviour (no options refresh when the parent changes), check config/app.php for the provider and run php artisan nova:publish / vendor:publish so assets land under public/vendor/.
Where to look first
README.md — the authoritative example for this package's exact method names (dependsOn, the options closure/array, any optionsFrom/using variants). Small packages rename things between minor versions; trust the README over blog posts.src/ — the field class (usually DynamicSelect extending Nova's Select). Read options(), dependsOn(), and resolve() to see exactly what gets sent to the browser and when the closure runs.resources/js/field.js — the front end. It tells you which request the field makes, what payload it posts (parent values), and whether responses are debounced.First use case: a cascading select
The canonical scenario is Country → City on a resource form: a normal Select for the parent, a DynamicSelect for the child whose option list is recomputed whenever the parent changes.
use Laravel\Nova\Fields\Select;
use Hubertnnn\LaravelNovaFieldDynamicSelect\DynamicSelect;
public function fields(Request $request)
{
return [
Select::make('Country', 'country_id')
->options(Country::pluck('name', 'id')),
DynamicSelect::make('City', 'city_id')
->dependsOn('country_id')
->options(fn ($countryId) => City::query()
->where('country_id', $countryId)
->orderBy('name')
->pluck('name', 'id')
->all()),
];
}
Smoke test: open the create form, change the country, and watch the network tab — you should see a request carrying the parent value and a fresh option list in the response. Then edit an existing record and confirm the stored city_id still preselects.
Multiple parents. Most implementations accept an array; the closure then receives one argument per parent, in order.
DynamicSelect::make('Locality', 'locality_id')
->dependsOn(['country_id', 'region_id'])
->options(fn ($countryId, $regionId) => Locality::query()
->when($countryId, fn ($q) => $q->where('country_id', $countryId))
->when($regionId, fn ($q) => $q->where('region_id', $regionId))
->pluck('name', 'id')
->all()),
Multi-level chains. Country → Region → City works by chaining: Region depends on country_id, City depends on region_id. Keep the fields in dependency order in fields(); the JS wiring usually binds by field attribute name at render time.
One resolver, three consumers. Extract the query into a method or small class so the field options, your validation rule, and any displayUsing all read from the same source. This is the single biggest maintainability win with dynamic selects:
private function cityOptions(?int $countryId): array
{
return $countryId
? City::where('country_id', $countryId)->orderBy('name')->pluck('name', 'id')->all()
: [];
}
Validation stays yours. The field only supplies options; it does not validate. Pair it with Rule::in(array_keys($this->cityOptions($request->country_id))) or an exists:cities,id rule, otherwise a crafted request can submit any ID.
Edit forms and display. On the form the stored value preselects because Nova hydrates the field's value before the JS runs — but the option list for that value must be derivable from the parent, so the parent field must also be present and populated on the edit form. On index/detail, the options closure is not executed; you get the raw key unless you add displayUsingLabels() (if the field inherits it from Select), a displayUsing() callback, or model the relationship with BelongsTo for display purposes.
Reuse across resources. Because the field is just a class, wrap your common cascades in a small factory (CitySelect::make('city_id')) returning a configured DynamicSelect. That keeps form definitions readable when several resources share the same geography or taxonomy.
Testing. Render the form definition with Nova's assertFields helpers to catch wiring mistakes (wrong attribute name in dependsOn), and unit-test the resolver method directly — that's where the real logic lives. If the package exposes an endpoint for the AJAX refresh, feature-test it with an authenticated Nova user.
dependsOn takes the attribute, not the label. With Select::make('Country', 'country_id'), depend on country_id. This is the most common silent failure — the field simply never refreshes.[] and combine with ->nullable() plus a placeholder so the field is usable while the parent is unset.Cache::remember keyed on the parent value (and tenant) is usually enough.id => label, and avoid high-cardinality option sets without search or pagination. Watch p95 latency in the network tab before shipping.composer.json, smoke-test rendering/refresh/validation/save on every Nova upgrade, and be ready to fork or replace it (22 stars, zero dependents, single-maintainer risk). Keep it behind your own factory so swapping to a hand-rolled field is a one-file change.->default() only on top-level selects.php artisan nova:publish plus a hard refresh — Nova's compiled assets are aggressively cached and a stale public/vendor bundle looks exactly like a broken dependsOn.How can I help you explore Laravel packages today?