laravel-relationship-events: model events for Eloquent relationships
Missing relationship events for Laravel
At a glance
- What is it?
- laravel-relationship-events adds relationship lifecycle events to Laravel's Eloquent, covering one to one, one to many, many to many and the polymorphic variants, with traits that register callbacks in a model's boot method and an alternative path through dispatchable events and observers. It is MIT licensed and tracks each Laravel major with a matching package major.
- Who is it for?
- Adopt laravel-relationship-events if your application needs to react to what happens to a related record, such as creating a profile alongside a user, and you want to keep that logic in the model layer rather than in service calls. Skip it if Laravel's model events already cover what you need, since this package is specifically about the relationship side.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 148 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap the package fills
laravel-relationship-events is described in one line as missing relationship events for Laravel, and that phrasing tells you what to expect. Laravel's Eloquent model fires events for the model itself, such as creating, created, updating and updated, and it fires events for the operations on a relation, but it does not fire an event when a related record is created through a relationship. The practical consequence is that a piece of behaviour tied to a relationship, say that creating a profile should also create a billing record, has nowhere natural to live. You end up calling it explicitly from a controller or a service, which means the behaviour only happens if that particular code path runs. This package adds the missing events, and the version history shows it tracks Laravel closely, with the current release v5.0.0 for Laravel 13 support, v4.0.0 for Laravel 12, v3.0.0 for Laravel 11 and v2.x for Laravel 10. The compatibility table in the README is explicit about the matrix, with each package major pinned to a Laravel major and a PHP requirement.
| Package | Laravel | PHP |
|---------|---------|-----|
| `v5.x` | `13.x` | `^8.3` |
| `v4.x` | `12.x` | `^8.2` |
| `v3.x` | `11.x` | `^8.2` |
| `v2.x` | `10.x` | `^8.1` |
| `v1.x` | `6.x` - `9.x` | `^7.2` \| `^8.0` |The project is MIT licensed, the primary language is PHP, and the last push to the repository was on 2026-05-28, the same day as the v5.0.0 tag.
Installing it and picking a trait
Installation is a composer require, and the README gives a stable and a development branch.
composer require chelout/laravel-relationship-eventscomposer require chelout/laravel-relationship-events:dev-masterThe second step is to use the appropriate trait in your model, and the list of available traits covers every relation shape Eloquent supports. There are HasOneEvents, HasBelongsToEvents, HasManyEvents and HasBelongsToManyEvents for the plain relations, and then a full set of polymorphic ones: HasMorphOneEvents, HasMorphToEvents, HasMorphManyEvents, HasMorphToManyEvents and HasMorphedByManyEvents. The naming is the trait name with the Events suffix, and it maps one to one onto the relation method you would otherwise define, which means reading a model tells you which relation types are instrumented. The repository tree is conventional for a Laravel package, with a src/ directory for the implementation, a doc/ directory holding the per-relation documentation, a tests/ directory, a composer.json, a phpunit.xml.dist, a .php-cs-fixer.php and a .styleci.yml for code style, and a LICENSE.md.
Registering callbacks in the boot method
The usage pattern is a trait plus static callbacks registered inside the model's boot method, and the one to one example shows it in full. The trait is imported from the package's Concerns namespace, the class extends the Eloquent model, and the boot method calls parent::boot() before registering the callbacks.
use Chelout\RelationshipEvents\Concerns\HasMorphToManyEvents;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasMorphToManyEvents;
public static function boot()
{
parent::boot();
static::morphToManyAttached(function ($relation, $parent, $ids, $attributes) {
dump('morphToManyAttached', $relation, $parent, $ids, $attributes);
});
static::morphToManyDetached(function ($relation, $parent, $ids) {
dump('morphToManyDetached', $relation, $parent, $ids);
});
}
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}The morph to many example shows the same idea on a polymorphic relation, where the callbacks receive the relation name, the parent, the ids involved and the attributes, with the morphToManyAttached and morphToManyDetached callbacks taking different argument lists because attaching and detaching are different operations with different data. The class in that example also defines the tags relation itself, returning a morphToMany to Tag with the taggable morph name, so the relation and its events sit in the same class.
Dispatchable events, the alternative to closures
There is a second way to react to relationship events, and the README describes it as dispatchable relationship events fired via the dispatchesEvents property together with the HasDispatchableEvents trait.
This is the more conventional Laravel arrangement. Instead of a closure registered in the boot method, you map the event name to a class, and the framework constructs and dispatches that class when the event fires. The benefit is that the handler is a class you can test on its own and that can be discovered by tooling, and the drawback is the same one Laravel's own model events have, which is that the mapping lives in a property rather than next to the logic that registers it. The trait is imported from a Traits namespace while the relation traits come from Concerns, so the two kinds of extension are kept apart in the namespace layout, which is a small but useful signal when reading the source.
Observers as a third route
Starting from version 0.4 it is possible to use relationship events in Laravel observer classes, and the README says usage is very simple. You take the model classes from the one to one documentation, add the HasRelationshipObservables trait to the model, and define an observer class with methods named after the relationship events. The observer example handles hasOneCreating and hasOneCreated, and each method receives the parent model and the related model, with a docblock for each.
The observer then has to be registered in the boot method of your service provider, which is standard Laravel.
And from that point, creating the related record fires the events without any explicit call.
Three routes for the same thing, then. Closures for logic that lives with the model, dispatchable event classes for logic you want in its own class, and observers for logic you want in a class with no relation to the model at all. Which one you pick is a question of taste and of where your team already keeps such behaviour.
The documentation is organised by relation type
The README links a document per relation family, which is a sensible structure because the event names differ between them. There is a document for one to one relations, one for one to many, one for many to many and one for has many through relations, then the polymorphic set with one to one polymorphic, one to many polymorphic and many to many polymorphic. Each of those documents walks through a worked example, which the observer section refers back to when it says to take the User and Profile classes from the one to one relations document. That pattern of building the documentation around one running example per relation type means a reader can see the same application shape under each set of event names rather than having to reconstruct it. The has many through case is worth noting as a separate document, since that relation type is derived rather than declared on the model, and the events for it are less obvious than for a direct relation.
Where laravel-relationship-events is the wrong choice
The first limit is that the package adds a layer to the model layer, and layers on the model layer are exactly what tends to make a codebase harder to follow. A callback registered in boot, an event class mapped in a property and an observer method named after a relationship are three different places to look for the same behaviour, and a team that uses more than one of them has a discoverability problem. The second is that relationship events fire at the point of the relation call, which means the behaviour is still coupled to how the code writes to the relation, and a direct model save on the related table bypasses them entirely. If a record can be created outside the relation path, the event does not fire, and that asymmetry is the thing to think about before relying on it for anything important. The third is the version coupling. Each package major targets exactly one Laravel major, so a Laravel upgrade is a package upgrade, and staying on the current major means missing fixes that land on newer branches. The fourth is the state of the project, which the README is honest about in a single line under a Todo heading that reads tests, tests, tests. That is a small project with a thin test suite, which matters more here than in most packages because the package's job is to intercept framework internals, and framework internals change between majors. And the fifth is that the whole package is aimed at one framework and one object relational mapper, so a project with more than one persistence layer gets this behaviour in one place and nothing in the others.
Editorial conclusion
Adopt laravel-relationship-events if your application needs to react to what happens to a related record, such as creating a profile alongside a user, and you want to keep that logic in the model layer rather than in service calls. Skip it if Laravel's model events already cover what you need, since this package is specifically about the relationship side. Verify first which package major matches your Laravel version, since each major targets one Laravel release, and that the trait you need exists for the relation type you are using.
Frequently asked questions
What does laravel-relationship-events add to Laravel?
It adds relationship lifecycle events to Eloquent, so you can react when a related record is created, updated, attached, detached and so on through a relationship. The events are registered per relation type through a trait on the model, with traits covering one to one, one to many, many to many and the polymorphic variants including HasMorphToManyEvents and HasMorphedByManyEvents.
How do I install laravel-relationship-events and which version should I use?
Run composer require chelout/laravel-relationship-events, or composer require chelout/laravel-relationship-events:dev-master for the development branch. Each package major targets one Laravel major, with v5.x for Laravel 13 on PHP 8.3, v4.x for Laravel 12 on PHP 8.2, v3.x for Laravel 11, v2.x for Laravel 10 and v1.x covering Laravel 6 to 9.
What are the ways to handle a relationship event in laravel-relationship-events?
Three. You can register a closure in the model's boot method, for example static::hasOneSaved with a callback receiving the parent and related models. You can use the HasDispatchableEvents trait and map an event name to a class in the dispatchesEvents property. Or, from version 0.4, you can use the HasRelationshipObservables trait and handle the events in a Laravel observer class registered with User::observe in your service provider.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/chelout-laravel-relationship-events)Was this useful?