lupennat/nova-nested-many
Nova Nested Many adds a nested hasMany field to Laravel Nova, letting you create and edit related records inline within a parent form using a repeatable, sortable nested form UI, so complex one-to-many data can be managed without leaving the resource detail page.
Install (Nova license required first). Nova resolves from nova.laravel.com, so Composer auth must be in place (auth.json locally, a CI secret) before anything installs. There is no standalone build — if Nova isn't already running in the project, stop here.
composer require lupennat/nova-nested-many
Where to look first, in order:
NestedMany::make(...) signature and whether php artisan nova:publish is required after install.vendor/lupennat/nova-nested-many/src/ — the PHP field extends Nova's HasMany and is the whole server-side surface. Read it end-to-end before merging; it's small enough to do so in one sitting.vendor/lupennat/nova-nested-many/package.json and dist/js/ — note which Nova minor the compiled Vue was built against. This is the single most common failure mode.fields(NovaRequest $request) — this is what actually renders inline. The child resource, not the call site, is the real configuration surface.First use case — one relationship, scalar fields, behind a flag.
// ParentResource::fields()
NestedMany::make('Line Items', 'lineItems', LineItem::class)
->canSee(fn () => config('admin.nested_many'));
Then run the round trip by hand: create a parent with two child rows, edit one, delete one, and submit with a deliberate validation failure in a child to see where the error lands. Repeat as a non-super-admin in the same session — inline nested create paths frequently skip the child resource's createX policy check. Thirty minutes here tells you more than the README.
Drive nested fields from the child resource, not the call site. The package renders whatever LineItemResource::fields() returns, so customise fields there and keep the parent resource clean. The child must be a real, registered Nova resource so fields(), rules(), and the policy resolve.
Nest by nesting the field. A second level is NestedMany inside the child resource's fields(). Two levels is what adopters have shipped; treat three as unproven until you test it yourself.
Wrap the vendor class in your own factory from day one. Every resource should call your class:
// app/Nova/Fields/NestedRelation.php
class NestedRelation extends NestedMany {}
Swapping to an in-house Vue field, or back to Nova's native two-step flow, then becomes a one-file change. This is the highest-leverage thing you do on day one.
Gate rollout with canSee() or an env flag. A config-driven canSee means you pull the field with a revert, not a deploy, and you can run it for one operator before the whole team.
Prove transactionality or add it. Nova does not wrap relationship writes in a transaction and this package issues multiple inserts per submit. Either wrap the save in DB::transaction at the action level or write a forced-failure test against the package's fill path. Don't assume atomicity.
Keep nested validation on child fields. Nested ->rules() works because the server-side field hydrates a HasMany payload. Client-side error placement is the weak spot, so test invalid submissions explicitly rather than trusting the red text lands on the right row.
Keep nested fields boring. Text, number, select, date, boolean, and a plain BelongsTo to a small lookup table all behave. File/MediaLibrary uploads, searchable BelongsTo, and computed fields inside a nested row are where Nova field packages break — add each deliberately and test in isolation.
Keep the child resource first-class. Don't hide it from the sidebar just because it now lives inline. Support staff still need it for bulk edits, exports, and audit views; both surfaces share the same model and policies, which is a feature.
Write the smoke test before the field ships. A Nova feature test walking create-with-children, update-with-children, and child-delete is the only insurance you have — and it catches a Nova upgrade within minutes instead of in production.
package.json in the vendor directory and the build date of dist/ against your Nova minor. If they don't match, you rebuild locally — which requires the Nova source tree and a working Node toolchain. That's a real blocker, not a footnote.BelongsTo overrides) silently break each other. Audit your other field packages the first time you load the form.createX returns false. If the row saves, stop and patch. Repeat for update and delete.BelongsTo gets viaResource/viaRelationship from the wrong context. Nova thinks the outer resource is the "via" and the child row is the child, so scoped queries and searchable selects behave differently than at top level. Test before relying on them.->limit()-style option in the README and treat it as a hard guardrail, not a nicety.packages/, add a path repository to composer.json, pin the commit, and budget 1–2 engineer-days per Nova major upgrade. MIT means forking is free — fork before you depend on it, not after.dist/ built against the wrong Nova minor. Check the browser console for Vue registration errors first, then nova:publish.resolve/fill for payload shaping; override the child resource's fields() for conditional display of nested inputs; use ->meta() for anything the Vue side reads as a config flag. There is no plugin API — if the shipped behaviour doesn't fit, plan on editing the fork.composer.lock, and keep the field behind a flag so rollout is reversible without a deploy.How can I help you explore Laravel packages today?