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

Laravel Nova Belongsto Dependency Laravel Package

sanderbaas/laravel-nova-belongsto-dependency

A Laravel Nova field that makes BelongsTo selects depend on another field's value, enabling cascading dropdowns in Nova resources. Pick a parent option and the dependent BelongsTo field updates its available options to match.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require sanderbaas/laravel-nova-belongsto-dependency

Because this is a Nova package, the parent laravel/nova repository must already be authenticated in composer.json. Check the package's composer.json for the Nova version constraint before installing on a Nova 4/5 app — the constraint is the first thing that will tell you whether this is even usable in your project.

Where to look first

  1. README.md — the usage snippet is the whole contract; there is very little else.
  2. src/BelongsToDependency.php — you want to see exactly which methods it adds on top of Laravel\Nova\Fields\BelongsTo (the dependsOn() signature is the one detail worth confirming rather than guessing).
  3. resources/js/field.js (or dist/js/field.js) — this is where the "dependency" actually lives. It watches a sibling field and appends its value to the associatable search request. Read it once; it's small.
  4. dist/js/ — the compiled asset that Nova actually loads. If dist/ is committed, you don't need to run npm. If it isn't, you do.

First use case — cascading Country → City

use Laravel\Nova\Fields\BelongsTo;
use SanderBaas\BelongsToDependency\BelongsToDependency;

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

        BelongsToDependency::make('City', 'city', City::class)
            ->searchable()
            ->dependsOn('country', function ($query, $countryId) {
                return $query->where('country_id', $countryId);
            }),
    ];
}

The closure receives the relateable query for City and the current value of the watched field; return the constrained query. The JS component takes care of re-firing the search endpoint when the watched field changes.

Implementation Patterns

Single dependency (the common case). Watch one sibling field, constrain the related model's query. Keep the constraint in a local scope on the related model so the same logic is testable outside Nova:

->dependsOn('country', fn ($query, $id) => $query->forCountry($id))

Multiple dependencies. Chain dependsOn() calls, or have the closure read several keys off the request. If the package only passes one value, fall back to the request:

->dependsOn('country', function ($query, $countryId) {
    $query->where('country_id', $countryId);

    if ($regionId = request()->input('region')) {
        $query->where('region_id', $regionId);
    }

    return $query;
})

Fail open on empty. On create forms the parent field is null on first render. Return the query untouched when the value is empty rather than where('country_id', null) — an unconstrained list is friendlier than an empty one, and Nova will still filter once the user picks a country.

Pair it with relatable() for hard limits. relatable() is evaluated server-side every request and is a good place to put authorization/tenant constraints; dependsOn() is for the dynamic, user-driven part. Don't duplicate the two.

Reusable field factory. If several resources share the same cascade, extract it:

public static function cityField(): BelongsToDependency
{
    return BelongsToDependency::make('City', 'city', City::class)
        ->searchable()
        ->dependsOn('country', fn ($q, $id) => $q->forCountry($id));
}

Index and detail views. The dependency is a form behaviour. On index/detail, the field renders like a normal BelongsTo — no filtering, no extra request. Don't expect it to constrain anything in filters or lenses.

Testing. The behaviour ultimately lives in one query closure. Write a feature test that hits Nova's associatable endpoint and asserts the returned resources:

$this->actingAs($user)
    ->getJson("/nova-api/cities/associatable/city?country={$country->id}")
    ->assertJsonFragment(['id' => $city->id]);

Also unit-test the closure directly by invoking it with a query builder — it's plain Eloquent, no Nova needed.

Inline creation. If you enable showCreateRelationButton(), be aware the created record won't automatically inherit the parent's value — the modal doesn't know about the dependency. Either hide inline creation on that field or pre-fill the foreign key in your model's creating event.

Gotchas and Tips

  • searchable() is mandatory. The dependency piggybacks on Nova's /nova-api/.../associatable/... search request. A non-searchable BelongsTo is a plain select and the package has nothing to hook into.

  • Field names must match exactly. The JS watches the sibling field by its request attribute (e.g. country, or country_id depending on how the field is named in fields()). A silent mismatch is the #1 cause of "it does nothing" — verify in the Network tab that the associatable request carries your dependency value.

  • Stale dependent values. Changing the country does not clear an already-selected city. If that matters, add a small custom JS listener (Nova.$on('field-changed')) to null out the dependent field, or accept the stale value and validate server-side.

  • Server-side validation is your job. Nothing enforces that the saved city belongs to the selected country. Add:

    Rule::exists('cities', 'id')->where('country_id', $request->country)
    

    This is also the only defence if the field is submitted by API clients rather than the Nova UI.

  • Authorization is inherited. The associatable endpoint respects viewAny on the related model. If a user legitimately can't search a relation, they'll see "no results", not an error — check policies before blaming the package.

  • Query cost. The closure runs on every keystroke-driven search request. Make sure the constrained columns are indexed, and avoid whereHas chains that turn a typeahead into a slow query.

  • Version/asset compatibility. This package predates Nova 4/5 asset handling in all likelihood. If the JS component fails to register, check nova:publish/vendor:publish tags, confirm dist/js/field.js exists, and verify the component name matches what Nova::script()/the service provider registers. Nova major-version mismatches usually surface as a blank field, not an exception.

  • Debugging path. Browser Network tab → find the associatable request → confirm the dependency query param is present and correct. If it is, the problem is in your closure; if not, the problem is in the JS/field-name binding. DB::listen() or Telescope will show you the generated SQL.

  • Abandonment risk. Zero stars, zero dependents, no recent activity. Treat it as a small piece of vendored code: pin the exact version, and be prepared to fork. The escape hatch is straightforward — a custom field extending BelongsTo that overrides the relateable query using request() input, plus ~20 lines of Vue to append the dependency value to the search request. Budget half a day for that fallback if the package breaks on your Nova version.

  • Extension points. Subclass BelongsToDependency and override the query-building method for cross-cutting behaviour; register additional JS with Nova::script() instead of editing vendor/. Never edit files under vendor/ — the next composer update erases them.

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.
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules
loco/loco
starfederation/datastar-php
yupmin/modern-phystrix
stephenjude/extended-artisan-commands
lumphp/laravel-macroable
lumphp/laravel-collections
lumphp/laravel-contracts
sermepa/sermepa
arkecosystem/fortify