Skip to main content

Documentation Index

Fetch the complete documentation index at: https://kawax.biz/llms.txt

Use this file to discover all available pages before exploring further.

What are model events?

Eloquent models automatically fire events at each stage of their lifecycle. You can hook into these events to run code before or after a model is saved, deleted, and so on.
EventWhen it fires
retrievedAfter a model is fetched from the database
creatingBefore a new model is saved for the first time
createdAfter a new model is saved for the first time
updatingBefore an existing model is saved
updatedAfter an existing model is saved
savingBefore either a create or an update
savedAfter either a create or an update
deletingBefore a model is deleted
deletedAfter a model is deleted
trashedAfter a model is soft-deleted
forceDeletingBefore a model is permanently deleted
forceDeletedAfter a model is permanently deleted
restoringBefore a soft-deleted model is restored
restoredAfter a soft-deleted model is restored
replicatingWhen replicate() is called on a model
Events ending in -ing fire before the change is persisted. Events ending in -ed fire after.
Mass updates and mass deletes (User::where(...)->update(...)) do not fire saving, saved, updating, updated, deleting, or deleted events because the models are never retrieved from the database.

Closure-based event listeners

For simple cases, register closures inside the model’s booted method.
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected static function booted(): void
    {
        static::created(function (User $user) {
            // Runs after the user is created
        });

        static::deleting(function (User $user) {
            // Runs before the user is deleted
        });
    }
}
To run a closure on a queue instead, wrap it with the queueable helper.
use function Illuminate\Events\queueable;

static::created(queueable(function (User $user) {
    // Runs asynchronously on a queue
}));

The $dispatchesEvents property

When you want model events to feed into Laravel’s event system, map them to dedicated event classes using $dispatchesEvents.
<?php

namespace App\Models;

use App\Events\UserDeleted;
use App\Events\UserSaved;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    /**
     * @var array<string, string>
     */
    protected $dispatchesEvents = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}
The mapped event class receives the model instance in its constructor.
<?php

namespace App\Events;

use App\Models\User;

class UserSaved
{
    public function __construct(
        public readonly User $user,
    ) {}
}

Creating an Observer class

When a model triggers several events, an Observer class is cleaner than scattered closures.
1

Generate the Observer with Artisan

Use make:observer with the --model option to get stubs for the relevant event methods.
php artisan make:observer UserObserver --model=User
This creates app/Observers/UserObserver.php.
2

Implement the event methods

Each method name corresponds to an event. The method receives the model instance.
<?php

namespace App\Observers;

use App\Models\User;

class UserObserver
{
    public function created(User $user): void
    {
        // Runs after a user is created
    }

    public function updated(User $user): void
    {
        // Runs after a user is updated
    }

    public function deleted(User $user): void
    {
        // Runs after a user is deleted
    }

    public function restored(User $user): void
    {
        // Runs after a soft-deleted user is restored
    }

    public function forceDeleted(User $user): void
    {
        // Runs after a user is permanently deleted
    }
}
3

Register the Observer

There are two ways to register an observer. The #[ObservedBy] attribute is preferred in Laravel 13.Option 1: #[ObservedBy] attribute (recommended)Add the attribute to the model class. No changes to AppServiceProvider are needed.
<?php

namespace App\Models;

use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Foundation\Auth\User as Authenticatable;

#[ObservedBy(UserObserver::class)]
class User extends Authenticatable
{
    //
}
To attach multiple observers, repeat the attribute or pass an array.
#[ObservedBy(UserObserver::class)]
#[ObservedBy(AuditObserver::class)]
class User extends Authenticatable
{
    //
}
Option 2: Register in AppServiceProvider
<?php

namespace App\Providers;

use App\Models\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        User::observe(UserObserver::class);
    }
}
The #[ObservedBy] attribute lives in the Illuminate\Database\Eloquent\Attributes namespace. It is a native PHP 8.0+ attribute and the approach encouraged in Laravel 13.

Running Observers after a transaction commits

When a model is created or updated inside a database transaction, you may want the observer to run only after the transaction commits successfully. Implement ShouldHandleEventsAfterCommit to get this behavior.
<?php

namespace App\Observers;

use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;

class UserObserver implements ShouldHandleEventsAfterCommit
{
    public function created(User $user): void
    {
        // Runs after the transaction commits
    }
}
If the event fires outside a transaction, it runs immediately as normal.

Temporarily disabling events

withoutEvents — silence all events for a block of code

Inside the closure passed to withoutEvents(), no model events fire.
use App\Models\User;

$user = User::withoutEvents(function () {
    User::findOrFail(1)->delete();

    return User::find(2);
});

saveQuietly — save without firing events

$user = User::findOrFail(1);

$user->name = 'Victoria Faith';

$user->saveQuietly();
Similar quiet methods exist for other operations.
$user->deleteQuietly();
$user->restoreQuietly();

Practical use cases

Clearing cache automatically

<?php

namespace App\Observers;

use App\Models\Post;
use Illuminate\Support\Facades\Cache;

class PostObserver
{
    public function saved(Post $post): void
    {
        Cache::forget("post:{$post->id}");
        Cache::forget('posts:latest');
    }

    public function deleted(Post $post): void
    {
        Cache::forget("post:{$post->id}");
        Cache::forget('posts:latest');
    }
}

Recording an audit log

getDirty() returns the attributes that have changed since the model was last synced.
<?php

namespace App\Observers;

use App\Models\AuditLog;
use App\Models\User;

class UserObserver
{
    public function updating(User $user): void
    {
        AuditLog::create([
            'model_type' => User::class,
            'model_id'   => $user->id,
            'changes'    => $user->getDirty(),
            'user_id'    => auth()->id(),
        ]);
    }

    public function deleted(User $user): void
    {
        AuditLog::create([
            'model_type' => User::class,
            'model_id'   => $user->id,
            'changes'    => ['deleted' => true],
            'user_id'    => auth()->id(),
        ]);
    }
}
The updating event fires before the record is saved to the database, so getDirty() contains the pending changes. Calling getDirty() inside an updated listener will return an empty array because the model has already been synced.
<?php

namespace App\Observers;

use App\Models\Order;

class OrderObserver
{
    public function created(Order $order): void
    {
        foreach ($order->items as $item) {
            $item->product->decrement('stock', $item->quantity);
        }
    }

    public function deleted(Order $order): void
    {
        foreach ($order->items as $item) {
            $item->product->increment('stock', $item->quantity);
        }
    }
}

Next steps

Eloquent scopes

Learn how local and global scopes let you reuse query constraints across your application.
Last modified on March 28, 2026