ardalanamini/laravel-custom-relation
A Laravel package that lets you define custom Eloquent relationships beyond the built-in hasMany, belongsTo, and morphTo types. Build relations with your own query logic and constraints, keeping complex model associations flexible and readable.
hasMany/belongsTo/morphMany can't express cleanly — composite keys, tenant-scoped joins, time-windowed associations ("posts by the same author published in the last 30 days"), or computed relations — teams currently choose between duplicating whereHas/query-scope chains across controllers or writing bespoke query objects. This package is a third option: express the relationship once, on the model, and reuse it like a native relation.whenLoaded()), so serialization contracts stay uniform instead of one endpoint hand-rolling a query and returning a differently-shaped payload.composer.json change from a first-time contributor. No migrations, no config publishing, no service provider, no runtime service. That means the decision to trial it can be made at engineering-lead level, not as an architecture review item. It's reversible with composer remove.Relation internals, it is sensitive to Laravel major versions. This belongs in the pre-upgrade evaluation queue, not the post-upgrade firefighting queue.Consider it when:
whereHas clauses across multiple call sites. Name them before you start; if you can't list them, you don't have the problem.Relation internals. Authoring a custom relation requires understanding how getResults() / addConstraints() compose and how eager-loading keys are matched; consuming one takes under an hour, authoring one takes one to two focused days."0.1.5", not ^0.1.5) and treat every future release as a potential breaking change requiring a changelog read.Look elsewhere when:
hasMany/belongsTo/morphMany already express it, this adds indirection with no payoff.withCount(), constrained eager loading, or has()/whereHas() and haven't verified support through a spike. Partial support here produces silent N+1s or wrong aggregates in production — failures that surface as latency, not exceptions.Recommended entry motion: spike against one low-traffic service. Extract a single relation behind a feature flag, prove parity against the existing scope with a query-count and result-set comparison test, then decide on expansion. Do not spread the package's API across many models until it has survived one framework upgrade cycle in a branch.
For executives — the one-liner: A small, reversible library that lets engineers define unusual database relationships once instead of copy-pasting the same join logic across the codebase. It costs almost nothing to trial and almost nothing to remove.
The business problem. When a data relationship doesn't fit Laravel's built-in patterns, engineers re-write the same query in several places. Those copies drift. Drift causes two visible symptoms: inconsistent API responses (the same concept shaped differently on different endpoints) and slow pages that nobody notices until traffic grows, because a badly-composed relation silently turns one database query into hundreds.
Why act now, and why act small. This is not a platform bet. The current release is a packaging change only — no new behaviour, no risk to existing systems. Adoption is purely additive: nothing in your schema, data, or existing relations is touched. A trial is one engineer, one relation, one feature flag, and a test that compares the old query's output to the new one's. Abandonment is a composer remove and reverting the models that referenced it.
What we're actually asking for. Not "adopt this library." We're asking for a bounded spike: pick three to five relations currently duplicated across the codebase, convert one, and measure. The deliverable is a decision — expand, hold, or drop — backed by query counts and a parity test, not an opinion.
The honest risks, stated plainly. The package is early-stage (0.1.x) and single-maintainer, and it plugs into a part of the framework that changes between major versions. There are four ways this goes wrong, in order of severity: silently wrong query results that row-count tests won't catch; simultaneous breakage of all custom relations on a Laravel upgrade; the project going stale and blocking future upgrades; and a version pin surprise in CI. Each has a cheap mitigation — parity tests, an exact version pin, keeping the surface area small enough to reimplement in-house in days rather than weeks, and a named internal owner.
For engineering. The value is expressiveness and reuse: relationship logic lives next to the other relations on the model, is discoverable, and composes with with(), whenLoaded(), and API resources like anything native. The cost is that authoring one is a real skill — budget one to two days of paired work and require every custom relation to be documented with the SQL it's expected to produce. That single artefact carries most of the onboarding load and is the difference between a maintained abstraction and orphaned knowledge.
Recommendation: Approve a scoped spike in a low-traffic service before the next Laravel major upgrade. Decide on broader use only after it has survived one upgrade cycle in a branch and after confirming eager loading, existence queries, and withCount() behave correctly under load.
How can I help you explore Laravel packages today?