haider00125/belongs-to-many-field
A Laravel field package for managing belongs-to-many relationships in admin panels and forms. Attach, detach, and sync related records through a simple field, with support for searchable options and pivot data.
Reality check first. This is a 0-star, 0-dependent package whose last release was 2022-05-16. Nova's own Laravel\Nova\Fields\BelongsToMany already covers many-to-many editing (attach/detach modal, searchable, pivot fields). Treat this package as a UI alternative you must vet, not a default dependency. Before installing, open composer.json in the repo and check the illuminate/* and laravel/nova version constraints — a May-2022 field almost certainly targets Nova 3 (Vue 2) and will break on Nova 4/5 (Vue 3) unless the dist/ bundle was rebuilt.
Minimal install
composer require haider00125/belongs-to-many-field
php artisan nova:publish # if the field ships publishable assets
Nova auto-registers field service providers via extra.laravel.providers; if the field's JS doesn't appear, verify that block exists in the package's composer.json.
Where to look first, in this order
src/ — the field class. Note its FQCN and whether it's named BelongsToMany (which will collide with Nova's native import) or something like BelongsToManyField.resources/js/components/ and dist/js/ — confirm a compiled bundle is committed, and whether it's built with Laravel Mix (Nova 3) or Vite (newer).composer.json → require block — the Nova/Laravel version gate is the single biggest go/no-go signal.README.md — for the constructor signature and any ->options() / ->relationship() style modifiers.First use case. The only reason to reach for this over the native field is UI preference: a flat multi-select or checkbox list rendered inline on the form, instead of Nova's searchable dropdown-plus-modal. If your pivot table is simple (role_id, user_id — no extra columns) and the option set is small (< ~50 rows), that's the sweet spot. If you need pivot attributes, async search, or filtering, stop here and use the native field.
use Haider00125\BelongsToManyField\BelongsToMany; // verify FQCN in src/
public function fields(NovaRequest $request)
{
return [
BelongsToMany::make('Roles'),
];
}
Alias imports to avoid the name collision. If the package class is BelongsToMany, you cannot import it alongside Laravel\Nova\Fields\BelongsToMany in the same resource. Alias one:
use Haider00125\BelongsToManyField\BelongsToMany as BelongsToManyField;
use Laravel\Nova\Fields\BelongsToMany; // used elsewhere on the resource
Standard resource wiring. The field resolves its options from the relationship name passed to make(), so the relationship method on the model must exist and be a real belongsToMany:
// app/Models/User.php
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class);
}
Scoping the option list. Where the package exposes option/modifier methods, apply them in fields() rather than globally so different resources can present different subsets:
BelongsToManyField::make('Roles')
->options(Role::where('active', true)->pluck('name', 'id')) // if supported
->rules('required', 'array'),
Confirm the exact method names against src/ — this is where README drift is most likely on an unmaintained package.
Pair it with the native field for pivot data. A robust pattern for day-to-day work: use this field for the simple relationship and keep a native BelongsToMany with ->fields() on the same relationship only if you need pivot editing. If both appear, guard the native one with ->hideFromIndex() / ->onlyOnDetail() so the two UIs don't fight over the sync payload on save.
Index and detail views. Custom multi-select components frequently render poorly in the index table. Expect to need:
->hideFromIndex()
->displayUsing(fn ($value) => $this->roles->pluck('name')->join(', '))
Testing. Since the package has no dependents and no test suite of note, write your own feature test around the save path before shipping — this is the highest-value hour you'll spend:
public function test_roles_sync_on_update()
{
$user = User::factory()->create();
$this->actingAs($admin)
->putJson("/nova-api/users/{$user->id}", ['roles' => [$roleA->id, $roleB->id]])
->assertOk();
$this->assertEqualsCanonicalizing([$roleA->id, $roleB->id], $user->fresh()->roles->pluck('id')->all());
}
laravel-nova Vue 2 mixins will fail at runtime with component registration or this.$parent errors. Test in a throwaway Nova 4/5 app before committing.Cannot use ... because the name is already in use, or silently the wrong field renders.dist/ may be stale or missing. If resources/js has changes not reflected in dist/js, the browser loads old code. Fix by forking and running the package's own build, or scaffold a local field with php artisan nova:field acme/many-to-many and port the component — often faster than resurrecting this one.sync() semantics. Custom fields sometimes bypass Nova's BelongsToMany sync and write the pivot themselves. Verify detach behavior on save; a field that only attaches and never detaches will silently accumulate stale pivot rows.php artisan nova:publish then confirm the JS file appears under public/vendor/; (3) inspect the outgoing PUT /nova-api/... payload in the network tab — if roles is absent, the problem is client-side, not the model.path repository) so composer update can't yank it and so you can patch the Vue component yourself.Laravel\Nova\Fields\BelongsToMany plus ->relatable(), ->searchable(), and a small custom field if you truly need checkbox UI. Before adopting an unmaintained field, prototype the native equivalent for 30 minutes — the delta is usually smaller than the maintenance cost.How can I help you explore Laravel packages today?