Laravel Magazine
Muting Eloquent Events with withoutEvents() and saveQuietly()

Muting Eloquent Events with withoutEvents() and saveQuietly()

Eric Van Johnson ·

You've wired up an observer that sends a notification every time an Order is updated. Great, until someone runs a one-off script to backfill 40,000 historical orders and your notification queue — and your customers' inboxes — get absolutely buried. Eloquent gives you a clean way to opt specific operations out of the event system without ripping the observer out.

The problem in miniature

class OrderObserver
{
    public function updated(Order $order): void
    {
        Notification::send($order->customer, new OrderUpdatedNotification($order));
    }
}

This is exactly the behavior you want for real customer-facing updates. It is not what you want when a migration script touches every row to backfill a new column.

saveQuietly(): one model, no events

foreach ($ordersNeedingBackfill as $order) {
    $order->shipping_zone = ZoneResolver::resolve($order->postal_code);
    $order->saveQuietly();
}

saveQuietly() persists the model exactly like save(), but suppresses every Eloquent event that would normally fire — saving, updating, updated, saved, the works. Observers listening for those events simply don't run for this call. There's also deleteQuietly(), restoreQuietly(), and forceDeleteQuietly() for the same suppression on other lifecycle actions.

withoutEvents(): a whole block, no events

When you're touching many models across a block of code, wrapping each call in saveQuietly() gets repetitive. Model::withoutEvents() mutes events for everything inside its closure:

use App\Models\Order;

Order::withoutEvents(function () {
    Order::whereNull('shipping_zone')
        ->cursor()
        ->each(function (Order $order) {
            $order->shipping_zone = ZoneResolver::resolve($order->postal_code);
            $order->save();
        });
});

Everything inside that closure — including save() calls that would normally fire the full event chain — runs silently. Once the closure returns, event dispatching goes back to normal for the rest of the request or command.

A seeder example

Seeders are the other classic case. If your UserObserver sends a welcome email on created, you really don't want that firing 500 times while seeding a local dev database:

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;

class UserSeeder extends Seeder
{
    public function run(): void
    {
        User::withoutEvents(function () {
            User::factory()->count(500)->create();
        });
    }
}

The line you shouldn't cross

Muting events is a tool for "this is bulk, administrative, or one-off data manipulation," not a substitute for fixing an observer that's too aggressive. If your normal application flow regularly needs to bypass its own event listeners to behave correctly, that's usually a sign the observer is doing something it shouldn't — like sending a notification on every minor field update instead of only the ones that matter. Reach for withoutEvents() and saveQuietly() for scripts, imports, and seeders; keep your real request-response cycle honest with the events firing as designed.

Stay Updated

Subscribe to our newsletter

Get latest news, tutorials, community articles and podcast episodes delivered to your inbox.

Weekly articles
We send a new issue of the newsletter every week on Friday.
No spam
We'll never share your email address and you can opt out at any time.