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 Belongs To Dependency Laravel Package

webparking/nova-belongs-to-dependency

A Laravel Nova package that makes BelongsTo fields depend on other form fields, enabling cascading or filtered selects. Choose a parent value and the related BelongsTo options update accordingly, keeping Nova forms dynamic and consistent.

View on GitHub
Deep Wiki
Context7

Getting Started

This package replaces Nova's native BelongsTo with a version that narrows its options based on another field on the same form. It is a single-purpose field: parent field changes → child field reloads. That's the whole product, and it's the reason it fits into a day of work rather than a sprint.

Install and pin

composer require webparking/nova-belongs-to-dependency:^2.0.1

Pin at ^2.0.1 deliberately. 2.0.1 is a patch that repairs component registration for the Index/Detail/Form views; on 2.0.0 you get blank columns and Failed to resolve component noise in the console. There is no feature difference between the two — don't go looking for one.

Nothing to publish in the normal case. The service provider registers the compiled assets with Nova::script() / Nova::style(). If a previous developer ran vendor:publish for this package, delete the published copies so the provider stays authoritative — otherwise you can end up loading a stale copy that contradicts the composer version.

Then the usual post-install ritual before you judge anything: php artisan view:clear && php artisan config:clear, hard-refresh, restart Octane/FrankenPHP workers if you run them.

First use case — the smallest thing that proves it works

On a Venue resource, make Country an ordinary searchable BelongsTo, then make State the dependency field:

use Laravel\Nova\Fields\BelongsTo;
use Webparking\NovaBelongsToDependency\BelongsToDependency;

public function fields(NovaRequest $request): array
{
    return [
        BelongsTo::make('Country', 'country', Country::class)->searchable(),

        BelongsToDependency::make('State', 'state', State::class)
            ->dependsOn('country'),
    ];
}

Create a venue: pick a country, watch the state list narrow. Edit that venue: the state list must populate from the persisted country with no user interaction. Those two checks are your smoke test for the rest of your life with this package.

Where to look, in order

  1. The README usage block. The public API is essentially make() plus one dependency method. Read it before copying snippets — confirm the exact method name and argument signature rather than trusting a blog post or a memory.
  2. src/BelongsToDependency.php. This is the file you fork if you ever need different query behaviour. Read it once and you'll understand every behaviour you'll later debug.
  3. The service provider. Confirms the asset registration that 2.0.1 fixed.
  4. The three Vue components (FormField.vue, DetailField.vue, IndexField.vue). One read-through answers the question you'll ask repeatedly: "why is this empty in a modal?" Because it is driven entirely by sibling form state, and modals don't have it unless you put it there.

Implementation Patterns

Two-level cascade — 90% of usage. Parent plain, child dependent. Country → State, Category → Subcategory, Client → Project, Team → Member. No customization needed beyond the two field declarations.

Three-level chains. Chain the same pattern: city depends on state, state depends on country. Declaration order must be strictly parent → child → grandchild. Test the middle level changing specifically — the grandchild should clear and reload. If it doesn't, that's a genuine finding to raise upstream or fix in a fork, not something you're doing wrong.

Layer it on top of Nova's existing query hooks. The dependency filter composes with your relationship constraints, so relatableQueryUsing() still applies. Use this on purpose:

BelongsToDependency::make('Project', 'project', Project::class)
    ->dependsOn('client')
    ->relatableQueryUsing(fn (NovaRequest $request, Builder $query) =>
        $query->where('archived', false)
    )
    ->searchable(),

searchable() + a constrained query is the answer to large child tables: the dropdown becomes a server-side search over a small result set rather than a giant option list rendered into the page.

Treat the update form as the real acceptance test. On create, the child list can appear correct because the user drove the parent. On edit, the parent is pre-set and the child must resolve from the persisted value with zero interaction. If create works and edit is empty, your dependency argument doesn't match the parent field's attribute, or the parent field isn't in the current field set (onlyOnDetail(), different panel, hidden by a canSee).

Mirror the dependency in validation — always. The UI narrows the choice; it does not enforce it. A stale form post or a crafted request can persist a state from the wrong country.

'state' => [
    'nullable',
    Rule::exists('states', 'id')->where('country_id', $this->country?->id),
],

Decide nullable explicitly. "Clear the parent, leave the child" and partial saves both hit this rule.

Modals and actions need the parent present. Nova action fields() return only what you list. A modal with just the child field renders a full or empty option list because there is no sibling state to read. Options: include the parent in the modal's field set, use standalone mode, or precompute the value in PHP and skip the cascade in that context.

Index/detail are safe to show. After 2.0.1 the field resolves and displays its related model like a normal BelongsTo. Don't reflexively add ->onlyOnForms() — that removes the label along with the column.

Adopt a resource-level convention so the pattern is discoverable to the next developer who won't read the package source: parent and dependent fields adjacent in fields(), dependency argument named identically to the relationship key, one comment line above the cascade describing the chain.

Testing split. Feature-test the Nova API for store/update, asserting both valid and invalid pairings are handled (the invalid one should fail via your Rule::exists). The reload itself is client-side — HTTP tests won't touch it. Cover that with Dusk or a manual QA checklist, and add a smoke check that the form renders with no console errors after deploy. That last one is the cheapest regression signal you have for asset-registration breakage.

Gotchas and Tips

  • The dependency argument is the field attribute, not the foreign key column. ->dependsOn('country') pairs with BelongsTo::make('Country', 'country', ...). Name the column country_id in the same array and you get a silently empty dropdown with no error anywhere.
  • Declaration order and co-location matter. Parent before child in fields(), and on the same form. Moving the parent into another panel or hiding it breaks the dependency in a way that looks exactly like a package bug — check your field set before you check the package.
  • Stale assets are the number one deployment failure. Every version bump: view:clear, config:clear, restart Octane/FrankenPHP, hard-refresh. "Component not resolving" is almost always cache, not code.
  • Debug the request, not the Vue component. When a dependent list is wrong, open the network tab and look at what parent value the child's option lookup actually carried. That single step splits the problem into "parent state never sent" (form/order/scope) versus "query returned wrong rows" (relatableQueryUsing, global scopes) and usually ends the investigation immediately.
  • Global scopes and soft deletes bite on edit. A previously selected child that is now scoped out or trashed renders empty and can be silently dropped on save. Add withTrashed() or relax the scope inside relatableQueryUsing() wherever historical values must remain selectable.
  • Stay inside the scope. Standard Eloquent belongsTo chains only. BelongsToMany, MorphTo, non-Eloquent option sources, and heavy multi-tenant scoping are custom work — bending the package costs more than writing the field.
  • Performance is about option size, not the package. Runtime overhead is negligible. A child table with tens of thousands of rows is a bad dropdown regardless of library: use ->searchable() and make sure the foreign key you filter on is indexed.
  • Extend by subclassing, not patching vendor. class MyDependentField extends BelongsToDependency and override the option-query building, then register your subclass in fields(). Keeps composer update painless.
  • Weigh the maintenance reality honestly. Last release February 2023, small contributor base, no vendor SLA. That's fine for internal admin surfaces; it's not fine as an unexamined dependency in a revenue-critical workflow. Pin the constraint, name an internal owner, add "re-validate the dependency fields" to your Nova-major-upgrade checklist, and note that the code surface is small enough that forking is an afternoon rather than a quarter.
  • Have the fallback written down. If a cascade sits in front of a workflow with an SLA, document how to swap the dependent field back to a plain BelongsTo (loosen validation, widen the option query) so an incident isn't also a design meeting.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect