genealabs/laravel-nova-morph-many-to-one
A Laravel Nova field that adds polymorphic many-to-one relationship support, letting you attach and manage morph-related models directly within Nova resource forms and detail views.
Install the package the usual way and confirm the constraint resolves against your Nova major before writing any field code:
composer require genealabs/laravel-nova-morph-many-to-one
There is no service provider to register manually if the package ships Laravel package discovery (verify by checking composer.json for extra.laravel.providers). The first place to look is src/ — you want the field class FQCN (typically something like GeneaLabs\LaravelNovaMorphManyToOne\MorphManyToOne) and its resolve() / fill() methods, because those define exactly which foreign key columns get written on save.
Minimum viable setup, using a Post → Comment morphMany as the example (Comment has commentable_id / commentable_type):
// app/Nova/Post.php
use GeneaLabs\LaravelNovaMorphManyToOne\MorphManyToOne;
public function fields(NovaRequest $request): array
{
return [
// ...
MorphManyToOne::make('Comments')
->searchable(),
];
}
The first use case is exactly the gap it was built for: you have existing Comment rows you want to attach to a Post, and Nova's built-in MorphMany field only offers inline creation. Drop this field on the parent resource, open a Post, and use the selector to pick existing children instead of retyping them. Confirm the behaviour on a scratch record before rolling it out — the description states attach/detach with searchable selection, but the exact write semantics (whether it nulls commentable_id on detach vs. deletes the row) are the thing you actually need to know, and that lives in fill(), not the README.
Pair it with the native field, don't replace it. The most practical pattern is both fields on the same resource: Nova's MorphMany for the "create a new one inline" path, this package's field for the "attach an existing one" path. Users get both workflows and you avoid forcing everyone through a two-step create-then-attach dance.
Handle the create form deliberately. On Post creation the parent row doesn't exist yet, so an attach selector has nothing to point at until after the initial save. Either hide the field on create (->hideWhenCreating()) and let users attach on the subsequent edit screen, or confirm the package defers the write until after the parent is persisted. This is the single most common source of "why didn't my selection save?" reports with any attach-style Nova field.
Searchable is not optional at scale. ->searchable() adds a search endpoint backed by the related resource's searchableColumns(). On a comments table with tens of thousands of rows, non-searchable becomes an unusable dropdown. If the related resource has a large table, also define searchableColumns() on that resource narrowly rather than relying on a broad title match.
Keep authorization at the resource layer. Nova relationships respect the related resource's authorizedToView / authorizedToAttach policies. If a user can see the field but the attach silently fails, the policy on the related resource is the first suspect — not the field.
Attach/detach semantics belong in your model, not the field. Whichever way the package writes the morph columns, put the compensating logic (reindexing, cache invalidation, audit logging) in model events on Comment — saved / deleted / updated — so it fires regardless of whether the change came from Nova, a console command, or a seeder. Field-level hooks are brittle across package upgrades, especially for a pre-1.0 dependency.
Detail and index display. These fields typically render a comma-joined list on detail views. If you need richer presentation, override the resource's fields() on the Detail and Index views separately, or subclass the field's resolve to return a shaped array. Subclassing is the cleanest extension point — see Tips.
The repository is archived. This is the fact that should drive your day-to-day posture more than any other. There is no maintainer to ship Laravel 12+ support, no security response, and no bug fixes. Treat it as vendored code you own: pin the exact version, fork it into your VCS if you adopt it, and budget for replacing it (a custom Nova field is a few hundred lines) the moment the next Laravel or Nova major lands. Two stars and zero dependents also means you are effectively the only production consumer — bugs you hit, you fix.
Pre-1.0 semver. 0.x means minor bumps may break. Never let a dependency bot auto-merge this; disable auto-merge for the package specifically and read the diff on every update. Given the archive, there likely won't be updates — which is both the risk and the mitigation.
Nova major compatibility is unstated. The 0.4.0 note only claims Laravel 11. Nova 4 vs Nova 5 field APIs diverged in places; verify against your installed Nova version by loading the form and hitting the field's endpoint in a browser, not just by passing the test suite. A field that renders but whose search endpoint 500s is the classic failure.
The morph type column is your most likely bug. If the selector attaches records but they vanish on reload, check whether commentable_type is being written as the FQCN vs. a morph map alias. If you use Relation::enforceMorphMap(), the package must respect it — verify in fill(). Mixed map/non-map usage across your app is the usual root cause.
Debugging attach failures. The field posts to Nova's standard field endpoints. When a selection silently doesn't persist: (1) php artisan nova:check for resource/policy issues, (2) temporarily log in the field's fill() to see what's in the request, (3) confirm the field is in fields() for the view being used (Nova 4+ separates index/detail/creation fields via methods). Don't start by blaming the package — nine times out of ten it's a field not present on the form being submitted.
Testing is awkward. Nova fields aren't meaningfully unit-testable; test through feature tests that POST to the Nova update endpoint and assert the morph columns on the child rows. Write those tests before you'd ever need to fork-and-patch, so you have a safety net if you do.
Subclass rather than patch. The lowest-friction extension point is a custom field extending the package's class:
class AttachComments extends MorphManyToOne
{
public function fill(NovaRequest $request, $model)
{
// custom pre/post logic
return parent::fill($request, $model);
}
}
This keeps your customisations in app/Nova/, survives a fork, and makes the eventual migration to a hand-rolled field a rename rather than a rewrite.
Have an exit plan from day one. Because the package is archived and adds nothing you couldn't replicate, the pragmatic recommendation is: use it for the immediate need, but scope a hand-written Nova field as technical debt with an owner and a trigger date (next Laravel major, or next Nova major, whichever comes first). Given the description is one paragraph and the implementation is presumably small, the build-versus-adopt calculus is closer than the "drop-in" framing suggests.
How can I help you explore Laravel packages today?