dillingham/nova-attach-many
A Laravel Nova field for attaching many related models to a BelongsToMany relationship in one modal. Search, paginate, and create records inline, then attach or detach them without leaving the form. Includes customizable display, authorization, and pivot support.
Architecture fit. The package is a Nova field extension, not a platform component. It plugs into Nova's field pipeline, ships its own Vue assets, and requires no service-provider registration or infrastructure change. Architecturally it is thin: attach/detach semantics are delegated to Eloquent's BelongsToMany and to Nova's relationship authorization, so the package adds presentation (a searchable modal, live attach count, one-click detach) rather than new domain behavior. That is a good fit for an admin panel that already has many-to-many relations modeled correctly, and a poor fit for anything that needs pivot payloads, ordering, validation, or approval state — those belong to the native BelongsToMany field or a dedicated pivot resource.
The hard architectural constraint is scope: the string passed to AttachMany::make() must resolve to a BelongsToMany. HasMany/MorphMany will render nothing useful. This makes adoption a per-relationship decision, which is fine, but it means the field cannot become a blanket replacement for relationship UI.
Integration feasibility. High, with conditions. The documented path is ~ten minutes from composer require dillingham/nova-attach-many:^2.0 to a working detail screen: publish Nova assets, clear caches, add the field, ensure the related resource has a non-empty $title and a $searchable list, and confirm the parent policy exposes attachAny<Relationship>/attach<Relationship> plus viewAny on the related resource. Every one of those is a prerequisite, and every one fails silently — an empty modal is far more often a policy or $searchable gap than a package bug. Feasibility therefore depends less on the library and more on whether the team's Nova resources and policies are already in good shape.
Technical risk. Three risks, ranked by blast radius:
AttachMany field left in a create/update form participates in relationship syncing on save, and an untouched field may clear existing pivots — this must be verified against the 2.0.0 source before it ships anywhere but the detail view. Separately, there is no uniqueness enforcement in the UI, so repeated attach/detach cycles and concurrent editors can accrete duplicate pivot rows that corrupt counts and later sync() assumptions.Key questions to resolve before committing:
BelongsToMany::make()) and how cheap is it to execute?Stack fit. Laravel + Nova 4 + Composer, with assets flowing through Nova's publish step. No new services, no queues, no external dependencies, no schema changes required by the package itself. The only deploy-pipeline addition is php artisan nova:publish (plus optimize:clear), which should already exist for any Nova deployment and is the first thing to check when the field renders blank.
Migration path. Progressive, per-resource, and reversible in one line. Do not rip out BelongsToMany::make() everywhere at once. Keep the native field wherever pivot columns or per-row pivot UI are needed; swap in AttachMany only for the "attach/detach existing records, nothing else" case. This gives a side-by-side comparison on real data and a rollback that is a pin revert plus asset republish — no data operation, which is what keeps the whole bet low-risk.
Compatibility. Pin the major, never float: ^2.0 for Nova 4, frozen ^1.0 for Nova 3. Exclude the package from unattended composer update and treat any Nova major bump as its own ticket with a regression pass on every resource using the field. Record the validated Nova minor in the dependency inventory so the pairing is auditable rather than tribal knowledge.
Sequencing.
^2.0, install on staging, publish assets, clear caches.attachMany($relationship, $resource, $query)) that standardizes ->resource(), ->title(), and tenant scoping in one place. This is the cheapest way to realize the package's actual benefit — panel-wide consistency — and it makes scoping auditable.->onlyOnDetail(), with production-scale data.assertDatabaseHas/assertDatabaseMissing on the pivot table, plus a negative test proving a user without policy rights attaches nothing. Do not test the Vue component.Maintenance. Budget roughly one day per quarter plus a defined block on every Nova upgrade. This is a tactical admin accelerator, not a platform dependency, and should be tiered accordingly in the dependency inventory. The absence of a release cadence since 2022-07-25 means the team, not upstream, absorbs compatibility work.
Support. The dominant support cost is misdiagnosis, not breakage. Silent failures — empty modal, non-filtering search, no-op save — present as package bugs and are almost always relationship-shape, $title/$searchable, or policy gaps. Publish a short triage runbook in that exact order (relationship returns BelongsToMany → related resource resolvable → non-empty title → $searchable defined → attachAny<Relationship> allowed → check the search request in the network tab). Standardizing through the shared builder reduces both training load and support surface; per-resource drift reintroduces it.
Scaling. ->query() runs server-side and is the primary lever: keep the candidate set small and scoped rather than relying on client-side filtering. Pair it with a real $searchable list and a database index on the title column. Bulk attach is N inserts with no built-in cap, so validate selection size via Nova relationship validation, keep bulk operations to a sane ceiling, and wrap scripted attaches in DB::transaction() to bound lock contention. The pivot unique index is a scaling prerequisite, not a nicety — duplicates break counts and downstream sync().
Failure modes, ranked by damage:
->onlyOnDetail() until the fill/resolve behavior is verified.help() text and external activity logging.->onlyOnDetail() or ->hideFromIndex().Ramp-up. Ten minutes to a first working detail screen; roughly a day for a mid-level developer to be genuinely productive, because the real learning is Nova policy and relationship literacy, not the field's fluent API. The shared builder is what converts that one-day ramp into a repeatable pattern across the panel — and what makes the eventual fork-or-replace decision a contained piece of work rather than an archaeology project.
How can I help you explore Laravel packages today?