How7o
  • Home
  • Tools
  • Prank Screens
  • Learn
  • Blog
  • Contact
Reading: How to Delete Related Records in Laravel Eloquent
Share
How7oHow7o
Font ResizerAa
  • OS
Search
  • Home
  • Tools
  • Prank Screens
  • Learn
  • Blog
  • Contact
Follow US
© 2024–2026 How7o. All rights reserved.
How7o > Learn > Web Development > How to Delete Related Records in Laravel Eloquent
Web Development

How to Delete Related Records in Laravel Eloquent

how7o
By how7o
Last updated: April 19, 2026
6 Min Read
Deleting a Laravel user cascades to remove related posts, photos, and notifications
SHARE

When you delete a user, what should happen to their posts, comments, and uploaded files? Leaving orphaned rows in the database is a classic bug source — a request for “all comments by user 42” suddenly returns nothing even though rows with user_id = 42 are still sitting there. Laravel gives you two clean ways to cascade these deletes automatically: a database-level foreign key, or a model-level event listener.

Contents
  • TL;DR
  • The Problem: Orphaned Rows
  • Option 1: Database-Level Cascade (Recommended)
  • Option 2: Application-Level Cascade (Via Model Events)
  • Interaction With Soft Deletes
  • Troubleshooting
    • Cascade Didn’t Run
    • Bulk Delete Skipped Events
  • Frequently Asked Questions
  • Related Guides

Originally published August 30, 2022, rewritten and updated April 17, 2026.

TL;DR

Two options. Database-level: add ->cascadeOnDelete() on the foreign-key in the migration — the database takes care of it. Application-level: hook the deleting event on the parent model and call $this->posts()->delete() etc. for each relationship. Prefer the database level unless you need to fire Eloquent events on the child rows.

The Problem: Orphaned Rows

Given a User model with hasMany relationships:

class User extends Authenticatable
{
    public function notifications() { return $this->hasMany(Notification::class); }
    public function photos()        { return $this->hasMany(Photo::class); }
    public function posts()         { return $this->hasMany(Post::class); }
}

Calling $user->delete() removes the user row but leaves everything else behind. A SELECT * FROM posts WHERE user_id = 42 still returns rows — they just point at a user that no longer exists. The fix depends on where you want the cascade logic to live.

Option 1: Database-Level Cascade (Recommended)

Push the cascade into the foreign key. In the migration for each child table, add cascadeOnDelete():

Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    // ...
});

Now when a row is deleted from users, MySQL automatically deletes every row in posts, photos, and notifications that references it — in a single atomic operation. You don’t have to remember to update the model code later when you add a new related table.

Trade-off: Eloquent model events (deleting, deleted) do not fire for the cascaded children, because Laravel never sees them — the database deletes them directly. If any child has a deleted observer that dispatches a job (e.g. purging a file from S3), that observer won’t run. In that case, reach for the application-level approach.

Option 2: Application-Level Cascade (Via Model Events)

Use the deleting model event to remove related rows before the parent is deleted. The cleanest place is the model’s booted() method:

class User extends Authenticatable
{
    protected static function booted(): void
    {
        static::deleting(function (User $user) {
            $user->notifications()->delete();
            $user->photos()->delete();
            $user->posts()->delete();
        });
    }

    public function notifications() { return $this->hasMany(Notification::class); }
    public function photos()        { return $this->hasMany(Photo::class); }
    public function posts()         { return $this->hasMany(Post::class); }
}

Each ->delete() on a relationship fires Eloquent’s deleting/deleted events for every child, which is exactly what you want when those events have side effects.

Wrap the whole thing in a transaction so a failure halfway through doesn’t leave the database inconsistent:

DB::transaction(function () use ($user) {
    $user->delete();
});
Comparison of database-level cascade vs Eloquent event-based cascade for related row deletion in Laravel

Interaction With Soft Deletes

If the parent model uses the SoftDeletes trait, ->delete() only sets deleted_at — the row stays in the database, so database-level cascades don’t fire. Children won’t be cleaned up unless you:

  • Hook the deleting event and soft-delete the children too (if they also use SoftDeletes), or
  • Use forceDelete() when you want the children gone for good.

Troubleshooting

Cascade Didn’t Run

For database-level cascades, verify the foreign key actually has ON DELETE CASCADE — run SHOW CREATE TABLE posts; in MySQL to inspect it. An older migration that used foreign('user_id')->references('id')->on('users') without onDelete('cascade') has no cascade behavior.

Bulk Delete Skipped Events

User::where(...)->delete() bypasses model events — it runs a raw DELETE query. If you need events to fire, retrieve the users first and delete each one: User::where(...)->get()->each->delete();. This is slower for large batches but guarantees the deleting hook runs.

Frequently Asked Questions

Should I use database cascade or Eloquent events to delete related records?

Use database-level <code>cascadeOnDelete()</code> by default — it’s atomic, efficient, and self-documenting in the schema. Switch to Eloquent’s <code>deleting</code> event only when child models have observers that must run (e.g. deleting associated files from cloud storage).

Do Eloquent model events fire on database-level cascade deletes?

No. When the database deletes rows via <code>ON DELETE CASCADE</code>, Laravel never loads or deletes those models, so their <code>deleting</code>/<code>deleted</code> events don’t fire. If you rely on those events, cascade at the application level instead.

How do cascading deletes work with SoftDeletes?

<code>SoftDeletes</code> only sets <code>deleted_at</code> — the row stays, so the database doesn’t trigger a cascade. Hook the <code>deleting</code> event and soft-delete the children explicitly, or call <code>forceDelete()</code> to trigger the database cascade.

Why doesn’t my deleting event fire on Model::where(…)->delete()?

Because that’s a bulk delete — it runs raw SQL without instantiating any models, so no events fire. To trigger events for each row, retrieve the models first: <code>Model::where(…)->get()->each->delete();</code>.

Related Guides

  • How to Add Foreign Keys in Laravel Migration
  • Laravel updateOrCreate: Insert or Update Records in Eloquent
  • How to Check if a Record Exists in Laravel

For the full event lifecycle, see the official Laravel Eloquent events documentation.

TAGGED:EloquentLaravelmysqlphp

Sign Up For Daily Newsletter

Be keep up! Get the latest breaking news delivered straight to your inbox.
[mc4wp_form]
By signing up, you agree to our Terms of Use and acknowledge the data practices in our Privacy Policy. You may unsubscribe at any time.
Share This Article
Facebook Copy Link Print
Previous Article Laravel migration converting a MySQL column type from VARCHAR to DECIMAL without losing data How to Change a MySQL Column Type in Laravel Migration
Next Article Laravel user password being updated via artisan tinker with Hash::make How to Change a User Password in Laravel
Leave a Comment

Leave a Reply Cancel reply

You must be logged in to post a comment.

FacebookLike
XFollow
PinterestPin
InstagramFollow
Most Popular
Display PHP errors — ini_set + php.ini configuration
How to Display PHP Errors
May 10, 2026
PHP convert string to uppercase — strtoupper and mb_strtoupper
How to Convert a String to Uppercase in PHP
May 10, 2026
PHP string to float conversion with cast, regex cleanup, NumberFormatter
How to Convert a String to Float in PHP
May 10, 2026
PHP merge arrays without duplicates — union operator and array_unique
How to Combine Two Arrays Without Duplicates in PHP
May 10, 2026
PHP delete array element — unset, array_splice, array_filter, array_search
How to Delete an Element from a PHP Array
May 10, 2026

You Might Also Like

Laravel request inputs with prefix — filter request()->all() by Str::startsWith
Web Development

How to Retrieve Inputs with a Specific Prefix in Laravel Request

7 Min Read
Include Composer packages in plain PHP projects
Web Development

How to Include Composer Packages in Plain PHP Projects (Autoload + Example)

5 Min Read
WordPress posts by date range — date_query with after/before/inclusive
Web Development

How to Get Posts by Date Range in WordPress

7 Min Read
Laravel DataTables custom column search — filterColumn callback handles the search SQL
Web Development

How to Search Custom or Composite Columns in Laravel DataTables

8 Min Read
How7o

We provide tips, tricks, and advice for improving websites and doing better search.

Tools

  • Age Calculator
  • Word Counter
  • Image Upscaler
  • Password Generator
  • QR Code Generator
  • See all tools→

Pranks

  • Fake Blue Screen Prank
  • Hacker Typer
  • Fake iMessage Generator
  • Windows XP Crash Prank
  • Windows 11 Update Prank
  • See all prank screens →

Company

  • About Us
  • Blog
  • Contact
  • Privacy Policy
  • Terms of Service
  • Sitemap
© 2024–2026 How7o. All rights reserved.
Welcome Back!

Sign in to your account

Username or Email Address
Password

Lost your password?