Skip to main content
To add relationships to a consuming application’s models from a package, you can register how the relationship is created with resolveRelationUsing(). Without editing the model file directly, you can retrieve, eager load, and query by relationship existence just as you would with a regular relationship. This page is based on the official source of Laravel Framework v13.40.0. For relationship basics, see Eloquent relationships. For provider loading, see Package auto-discovery.

Choose between this and regular definitions

The official documentation recommends regular relationship methods for typical application development, and notes that dynamic definitions may be useful in cases such as package development. Dynamic relationship properties are different from the feature that registers relationship definitions from outside the model. Also, resolveRelationUsing() is not a macro that adds arbitrary methods. Design the callback to return an Eloquent Relation instance.

Register on the model chosen by the user

As an example, we add a hasMany named acmeNotes to the consuming application’s order model. This assumes the package namespace Acme\OrderNotes\ maps to src/ via Composer PSR-4 and is loaded as a regular non-deferred service provider.

Prepare the configuration and the related model

Set the default value in config/acme-order-notes.php to null. Installing the package alone does not register a relationship on the application’s models.
The package’s src/Models/Note.php is the related model.
The example assumes the orders table has an integer primary key id, and the acme_order_notes table has id, order_id, body, and timestamps. Add an index on order_id. If you use UUIDs or similar, make the types of the related columns match as well.
Registering a dynamic relationship does not create tables or foreign key constraints. It is a separate process from Publishing and updating package migrations. The provider below also does not include database access or migration runs.

Register the creation logic in boot

In src/OrderNotesServiceProvider.php, merge the configuration and register the callback on the target model in boot().
In the consuming application, publish the configuration and change order_model to the concrete model you actually use, such as \App\Models\Order::class. Configure provider loading with auto-discovery.
The foreign key order_id and the parent key are passed explicitly to hasMany(). This prevents Eloquent from guessing purchase_id even if the parent model’s class name is Purchase, and keeps it consistent with the package’s table definition.
The target model is configured by the application’s administrator. Do not design the package to receive class names or relationship names directly from HTTP requests or re-register them on every request. Also, registering on Eloquent’s base Model affects all of its descendant models.

Use it like a regular relationship

On the configured order model, calling the method returns a HasMany, and accessing the property returns the collection of results.
With create(), hasMany sets order_id, so you do not need to mix the foreign key into the array passed by the user. The inverse relationship is not added automatically, so define it separately if needed.

Do not run retrieval in the callback

If the callback returns the result of get() or a single model, it cannot be treated as a valid relationship, for example when accessed as a property. Keep building the relationship separate from retrieving data. Builder::getRelation() for eager loading builds the relationship on a new model instance. Therefore, avoid designs where the callback assumes “a retrieved order ID exists” or returns a different relationship definition depending on that ID. Dynamic registration does not eliminate the N+1 problem or lazy loading restrictions. When working with multiple models, load relationships with with() and the like as usual.

Be careful with relationship name guessing in belongsTo

When the relationship name is omitted, belongsTo() guesses it from the caller using a backtrace. When defining it from a closure, rather than relying on the same guessing as a regular model method, you can explicitly specify the relationship name as the fourth argument in addition to the foreign key and owner key. The following is a standalone example where the order has customer_id and the customer has id.

Understand the rules for name collisions and inheritance

HasRelationships stores closures in a static property keyed by the model’s class name and the relationship name. Model::__call(), which receives calls to nonexistent methods, looks up the resolver and runs it, passing the target model instance. The diagram shows the path when a resolver is found. Note that collisions with existing methods or attributes follow a different path. Use a unique name such as acmeNotes for the relationship and check for collisions with the user’s methods, attributes, and other packages. method_exists() alone cannot detect dynamic registrations. Eloquent’s isRelation() also checks resolvers, but it does not guarantee that there are no collisions with attributes.

Separate the lifetime of registered state from retrieved results

Resolver registrations and the loaded relationship data held by each model are different things. Overwriting a resolver after $order->acmeNotes has already been loaded does not automatically update the result held by that model. If you need to retrieve it again, remove the relationship result and then use load('acmeNotes') or similar.
Registrations are stored in a static property, not the service container. If the closure captures the request, the authenticated user, a tenant, or similar, it may be retained in long-lived processes. Limit registrations to stable definitions at boot time, and restart processes such as queue workers and Octane when configuration or code changes. See also Package service lifetime design.
Similarly, do not assume that recreating the application in Testbench clears resolvers. For tests that compare enabled and disabled states, use separate model fixtures or PHPUnit process isolation to avoid depending on earlier registrations.

Test both registration and SQL

With Orchestra Testbench, verify not only provider registration but also the foreign key and actual relationship retrieval. The following is an example of tests/DynamicRelationsTest.php. Make the package classes and test namespace loadable via Composer, and have the PDO and SQLite drivers available.
The test model is named OrderFixture, but the foreign key remains order_id rather than order_fixture_id. This confirms that it does not depend on guessing from the class name. In the package’s CI, also verify the following boundaries.
  • With a separate unregistered fixture, no relationship is added when order_model is null.
  • Inheritance to child models, and precedence when a resolver with the same name is registered only on the child.
  • The intended API is available when colliding with existing methods or attributes.
  • The difference between a model that has already loaded the relationship and a new model after the resolver is re-registered.
  • Column types, indexes, and foreign key constraints on supported databases, as well as primary keys changed by the user.

Primary sources

Last modified on November 4, 2026