kirschbaum-development/nova-inline-relationship
Laravel Nova field for creating related resources inline without leaving the form. Search and select existing records, or open a modal to add a new one. Supports BelongsTo, MorphTo, and HasOne relationships, with searchable selects, custom modal forms, and automatic parent refresh.
Architecture fit. This is a thin Nova field package, not a platform component. No migrations, no config file worth tuning, no service provider knobs, no queue or storage. It extends a pattern most Nova teams already use — custom/third-party field classes registered per resource — so it slots into the existing admin layer without touching application domain code. The surface is a composer dependency plus one field class per resource that wants it. For a TPM, that means the decision is scoped to internal tooling, carries no infrastructure cost, and is reversible in hours rather than sprints.
Integration feasibility. High. Install is a single composer require pinned to ^2.1.1; usage is a field declaration in fields(NovaRequest $request). The main compatibility gate is the Nova major version: the last real release (2.1.1, Jan 2024) predates widespread Nova 5 deployment, so Nova 5 must be treated as untested until the field has rendered in a browser. There is also a documented constructor-signature ambiguity (label, relationship, attribute) that warrants a ten-minute read of the small PHP field class before resource code is written.
Technical risk. The risk is not the render path — that is visible and cheap to fix. The risk is the write path. Inline editing turns an index table into a write surface, and three concerns land on the product owner, not just the engineer:
$timestamps on inline saves (models with $timestamps = false could receive timestamp writes against non-existent columns; normal models could save without touching updated_at). 2.1.1 fixed it. This was a production data-integrity bug, and it is pinned away by requiring ^2.1.1 rather than floating.perPage. Development at ten rows looks fine; five hundred rows is a latency ticket.Secondary risks: no sorting, filtering, or search on the inline attribute (presentation-layer only); undefined behaviour for dangling foreign keys; and a maintenance cliff at Nova majors, where the package's semver will not warn you before a break.
Key questions to answer before committing.
composer.json constraint?Model::save() — firing observers, model events, cache invalidation, and updated_at-driven jobs — or write around it?readonly() respected, and does it behave on index versus detail?$timestamps = false (legacy tables, view-backed models, pivot-like children)?Text::make(..., fn () => $this->relation->attr) closures this would consolidate?Stack fit. Laravel + Nova 4.x/5.x, PHP field class plus a Vue component, MIT licensed, 0 dependents on Packagist and 193 stars. It fits teams already shipping Nova fields. It is not a reason to adopt Nova, and it is not viable outside Nova.
Migration path — two phases, gated.
Phase 1 — read-only. Inline display of one or two related attributes on index views. Near-zero risk, immediate operator benefit (removes a click-through), and it is where most teams should stop. Success metric: no hand-rolled inline-resolution closures remain in the codebase.
Phase 2 — write surface. Inline create/edit enabled per field, with a named list of editable attributes, explicit rules(), and confirmed policy coverage on the related model. This phase gets its own review; it is not a natural continuation of Phase 1 and should not be inherited by default because the field happens to support it.
Compatibility and containment. Wrap the package field in an in-house subclass from day one. Every resource uses the wrapper; the wrapper is the single place to force read-only, normalise arguments, override resolution, and absorb a fork's behaviour. A Nova upgrade, an upstream stall, or a swap to a maintained fork becomes a one-file change instead of a sweep across a dozen resources. Register eager loading (public static $with or indexQuery) on every resource that uses it — this is the performance contract, not an optimisation.
Sequencing.
src/ and answer the seven questions above; record answers.^2.1.1.$timestamps = false child.perPage rises.Estimated effort for a Nova-fluent engineer: roughly half a day to first pilot resource, plus per-resource field work measured in minutes. Removal cost, if abandoned, is a refactor measured in hours.
Maintenance. Low cadence is the defining characteristic. One release in the visible window, contributor-driven. Mitigation is cheap and should be mandated: pin the version, read the changelog manually on each bump (the 2.1.1 entry is five lines and materially changed the risk profile), and keep the wrapper class so an upstream stall is survivable. Because the licence is MIT and the PHP class is small, forking is a realistic fallback rather than a last resort — budget it as such.
Support. Community-only, no vendor SLA. Kirschbaum is a recognised Laravel/Nova shop, which is a modest liveness signal, but there is no contractual escalation path. Internal ownership must be assigned to a named engineer or team; "the Nova ecosystem maintains it" is not an owner.
Scaling. Scales with operator count, not traffic — this is an admin surface. The one dimension that does scale badly is index row count: without eager loading, latency grows linearly with perPage and any dashboard or export hitting the same endpoint inherits it. Treat any resource already fighting N+1 as blocked, not as a candidate. Note also that a wrapped related field that is itself expensive (a File, a nested BelongsTo) drags its own query cost along.
Failure modes to monitor.
nova:publish).Ramp-up. Minimal for anyone who has written a Nova field: an hour of source reading, an hour of resource work, a pilot record. The non-obvious ramp-up cost is organisational — getting the write-path decision, the policy review, and the timestamp test cases into the team's Nova style guide so the next developer doesn't rediscover them. Ship with tests covering: field renders with null relation, field renders with populated relation, inline save updates updated_at on a timestamped child, inline save leaves timestamps untouched on a $timestamps = false child. A page-renders-200 test proves nothing here.
How can I help you explore Laravel packages today?