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 Child Select Laravel Package

alvinhu/nova-child-select

Laravel Nova field that adds cascading, dependent select dropdowns to your forms. Child options update dynamically based on a parent field's selection, so you can build linked selects (like country then city) with a simple fluent API and no custom JavaScript.

View on GitHub
Deep Wiki
Context7

Getting Started

Check compatibility before anything else. The package is archived, so treat it as frozen: open composer.json in the repo and confirm the illuminate/* / laravel/nova constraints against your app. It is a Nova 2/3-era field (Vue 2 component, pre-Inertia). On Nova 4+ it will not render — the front-end contract changed to Vue 3 + Inertia, and nothing in an archived repo will be updated for it. If you're on Nova 4, stop here and use the built-in dependsOn() plus server-resolved options() on a normal Select.

Install (Nova 2/3):

composer require alvinhu/nova-child-select

The service provider registers via auto-discovery. If you're on an older Laravel, add Alvinhu\ChildSelect\FieldServiceProvider::class to config/app.php.

Where to look first, in this order:

  1. README.md — the whole feature surface is one code sample.
  2. src/ChildSelect.php — the field class. This tells you the exact method names (->parent(...), ->options(...)), the $component string, and how options are serialized into meta.
  3. resources/js/components/FormField.vue — this is where the "child" behavior actually lives: it watches the parent field's value and filters the nested option map.
  4. resources/js/field.js — component registration, useful if you decide to fork.

First use case — country → city on a resource form:

use Laravel\Nova\Fields\Select;
use Alvinhu\ChildSelect\ChildSelect;

public function fields(Request $request)
{
    return [
        Select::make('Country', 'country')->options([
            'cn' => 'China',
            'us' => 'United States',
        ]),

        ChildSelect::make('City', 'city')
            ->parent('country')
            ->options([
                'cn' => ['bj' => 'Beijing', 'sh' => 'Shanghai'],
                'us' => ['nyc' => 'New York', 'sf' => 'San Francisco'],
            ]),
    ];
}

The outer keys of options() are parent values, the inner arrays are the child choices. The stored value is the inner key.

Implementation Patterns

Build the nested map from Eloquent, and cache it. The map is serialized into the field meta on every form resolution, so don't run N+1 queries per request:

ChildSelect::make('City')
    ->parent('country')
    ->options(Cache::rememberForever('cities_by_country', function () {
        return City::query()
            ->orderBy('name')
            ->get(['country_code', 'code', 'name'])
            ->groupBy('country_code')
            ->map(fn ($cities) => $cities->pluck('name', 'code'))
            ->toArray();
    })),

Keys must be the stored value of the parent field as a string — not the label, not the model ID unless the parent stores the ID.

Chaining for three levels (country → state → city). Each ChildSelect is itself a parent for the next one, so just point at the previous field's attribute:

Select::make('Country', 'country')->options($countries),
ChildSelect::make('State', 'state')->parent('country')->options($statesByCountry),
ChildSelect::make('City', 'city')->parent('state')->options($citiesByState),

This works because each field is just another Nova field with an attribute name; the component watches by attribute.

Scope to forms only. On index/detail the field usually falls back to rendering the raw stored key, which reads badly. Either hide it (->onlyOnForms()) or add a companion display field:

Text::make('City', function () {
    return $this->city ? City::find($this->city)?->name : null;
})->onlyOnDetail(),

Always re-validate server-side. The client-side filtering is presentation only; a crafted request can post any child value. Add a scoped rule:

ChildSelect::make('City', 'city')
    ->parent('country')
    ->options($citiesByCountry)
    ->rules(['required', function ($attribute, $value, $fail) use ($request) {
        $allowed = array_keys($citiesByCountry[$request->input('country')] ?? []);
        if (! in_array($value, $allowed, true)) {
            $fail('The selected city does not belong to the selected country.');
        }
    }]),

Know what dependsOn() does and doesn't do. Nova's built-in dependsOn() shows/hides fields based on another field's value — it does not filter a select's options. That gap is exactly what this package fills. If you only need conditional visibility, skip the package entirely and avoid the archived-code risk.

Gotchas and Tips

  • Numeric keys are the classic bug. PHP casts numeric-string array keys to ints ('1' => ... becomes 1 => ...), they arrive in JS as object keys (strings), and any strict comparison in FormField.vue between the parent value and the map key can silently fail. Symptom: the child is always empty for some parents but fine for others. Fix by prefixing keys ('c_1') or casting parent values to strings consistently.
  • Attribute name, not label. ->parent('country') must match the parent field's attribute, in snake_case. ->parent('Country') will resolve nothing and fail silently.
  • Stale child values. When the parent changes, the previously selected child may remain and be submitted. Nothing in the package guarantees a reset. Guard with the validation closure above, and/or clear it in saving() when the parent is dirty.
  • Defaults on create. If the parent has ->default(), confirm the child populates on first render; if not, set the child's default to a value valid for that parent, or leave it null and rely on ->rules('required').
  • Field scope matters. Fields inside HasMany, BelongsTo searchable sub-forms, or any independent form scope do not share reactive state with the top-level form. A ChildSelect nested in another field's sub-form will not see the parent — it will just show an empty or full option list.
  • Datasets are shipped with the page. There is no async/lazy loading. A full city table for every country becomes a multi-megabyte Nova form. If the option set is large, this is the wrong tool — build a custom Vue field with an AJAX endpoint.
  • Debugging sequence: (1) dump the serialized field with json_encode($field) in a tinker/test to confirm options and parent are what you expect; (2) open the Nova form request in the network tab and check the field's meta; (3) only then open FormField.vue. Ninety percent of failures are in step 1.
  • Extension point is a fork. Assets are precompiled and the repo is archived; there is no maintained publishable source to patch. If you need different behavior, scaffold your own field (php artisan nova:field acme/child-select) and copy FormField.vue as a starting point — that also lets you add async loading and a proper reset.
  • Pin it. With no upstream fixes, commit the version constraint ("alvinhu/nova-child-select": "1.0.2" or whatever you install) and add a note in your upgrade checklist to replace it before any Nova major upgrade.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package