digital-creative/custom-relationship-field
A Laravel Nova field for customizable relationship selection. Define how related records are searched and displayed, giving Nova resources flexible relationship inputs that fit naturally into forms while reducing the need for custom field code.
Architecture fit. The critical framing for any planning conversation: this is a Laravel Nova field, not an Eloquent relationship. It emulates HasMany/BelongsToMany in the admin UI only — search, select, attach, reorder — while the actual persistence is an ordinary column (realistically a JSON-cast attribute). That makes it a pure presentation-layer component with no footprint on models, APIs, or the database schema. For an internal, human-curated grouping (featured products, editorial collections, pinned resources) it fits cleanly and removes a migration + pivot + custom-field work package from the critical path. For anything that needs referential integrity, whereHas, eager loading, cascades, or visibility to a consumer outside the admin panel, it is the wrong tool and no amount of configuration fixes that. The fit test is one sentence: does anything other than a Nova screen need to know this relationship exists? If yes, stop.
Integration feasibility. High, with a caveat. The package is essentially one field class plus compiled Nova frontend assets; install is a single composer require, and usage is inline configuration on an existing resource (CustomRelationshipField::make(...)->options(...)). A competent Laravel dev can prove a round-trip on a throwaway resource in roughly half a day. The caveat is that the package reaches into Nova's index-query pipeline — the v1.1.3 release is a buildIndexQuery crash fix for bulk "select all" in a BelongsToMany context — which means it is coupled to Nova internals rather than only Nova's public field API. That single fact drives most of the risk below.
Technical risk. Ranked, because they are not equal:
->options() closure is a live data-leak vector, and it is the kind of thing that reviews miss because the code looks like UI configuration.whereIn does not preserve ordering, there is no eager loading, and index views can N+1 if the field resolves per row. Anything that assumes relational behavior — reports, exports, search indexing — breaks quietly.Key questions to close before committing:
composer.json constraints, not the release date.)Stack fit. Laravel + Nova is the only supported shape. No additional infrastructure, no queue, no external service. The only new persistent artifact is a column on the owning table. This is about as low-blast-radius as a feature integration gets — which is precisely why the risk is easy to under-manage: nothing about the deploy looks consequential, so the wrapper, the scoping, and the upgrade test step get skipped.
Migration path. Three scenarios, and the plan differs:
array, configure the field. No data movement, no dual-write window.Compatibility. Pin to ^1.1.3 at minimum — earlier versions carry a latent crash on bulk select-all that an editor will eventually hit. Pin the exact Nova version alongside it and treat them as a coupled pair in the lockfile. Verify compiled frontend assets are published during deploy; "renders as a blank box" is nearly always an asset/mix problem rather than PHP. Test select-all on the index view against realistic record counts, because the crash fix moved the question from does it work to what does it cost.
Sequencing (each step is a gate for the next):
fields() onto the model as a named method, with tenant/role scoping explicit inside it.Maintenance. This is the dominant ongoing cost and it should be stated plainly in any adoption decision. There is no upstream SLA, one visible maintainer, and a release line that went quiet after mid-2024. Upstream issues and PRs may go unread. The realistic operating model is: pin the version, keep the single-file seam, name an internal owner, and assume you patch Nova-major compatibility yourself. Budget it as a fork, not as a subscription you can escalate to.
Support. Self-support with good debuggability — the surface is small enough to read in ten minutes. Two diagnostic patterns cover most incidents: call the options closure from tinker to split "our query is wrong" from "Nova/asset is wrong," and check the browser console before debugging backend code. Worth writing into a short runbook alongside the resource, because the next person to touch it will not be the person who integrated it.
Scaling. The constraint is not traffic, it is set size. There is no pivot table, so every read is an application-side whereIn and every write materializes the full ID set into a JSON column. Select-all over thousands of records will produce a JSON blob the UI struggles to render and your controllers struggle to use — bound the option list (domain filter or a hard limit) so the UI cannot offer a set the storage cannot comfortably hold. Index views are the other scaling edge: a field that resolves per row turns a page render into N queries, so memoize options per request. Set an explicit ceiling (for example, low hundreds of links per record) and a re-evaluation trigger at which you build the pivot, or the ceiling will be discovered by an editor rather than decided by the team.
Failure modes — worth pre-agreeing the response to each:
->options() as an authorization boundary, not a UI convenience.whereIn returning arbitrary order is a silent defect. Mitigation: store position and re-sort in PHP, decided at design time.Ramp-up. Small — a developer unfamiliar with the package can read the entire source in one sitting, and the integration recipe is roughly one to two days of work covering the wrapper, the shared options method, tenant scoping, the cleanup job, and a contract comment in the resource stating what backs the field and that no API reads it. That comment is cheap and it is what turns a future migration into planned work rather than an incident. The cost to plan against is not onboarding; it is the recurring per-Nova-upgrade verification and the standing ownership of cleanup, scoping, and the eventual promotion decision.
How can I help you explore Laravel packages today?