How7o
  • Home
  • Tools
  • Prank Screens
  • Contact
  • Blog
Reading: How to Delete Related Records in Laravel Eloquent
Share
Subscribe Now
How7oHow7o
Font ResizerAa
  • Marketing
  • OS
  • Features
  • Guide
  • Complaint
  • Advertise
Search
  • Home
  • Tools
  • Prank Screens
  • Contact
  • Blog
Follow US
Copyright © 2014-2023 Ruby Theme Ltd. All Rights Reserved.
How7o > Blog > 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.
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

Subscribe Now

Subscribe to our newsletter to get our newest articles instantly!
Most Popular
Laravel user password being updated via artisan tinker with Hash::make
How to Change a User Password in Laravel
April 19, 2026
Deleting a Laravel user cascades to remove related posts, photos, and notifications
How to Delete Related Records in Laravel Eloquent
April 19, 2026
Laravel migration converting a MySQL column type from VARCHAR to DECIMAL without losing data
How to Change a MySQL Column Type in Laravel Migration
April 19, 2026
Laravel migration adding two new columns to an existing transactions table
How to Add New Columns to an Existing Table in Laravel Migration
April 19, 2026
Laravel foreign key constraint linking posts.user_id to users.id in a schema diagram
How to Add Foreign Keys in Laravel Migration
April 19, 2026

You Might Also Like

Send a simple email in Laravel using Mail::raw and SMTP
Web Development

How to Send a Simple Email in Laravel (Fast SMTP + Mail::raw)

4 Min Read
CSS print styles shown in a clean print preview layout
Web Development

How to Add CSS Print Styles for Printer and Print Screen

7 Min Read
Capitalize all words in JavaScript with a ucwords-style function
Web Development

Capitalize All Words in JavaScript (ucwords Equivalent) + First Letter Uppercase

6 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
How7o

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

Latest News

  • SEO Audit Tool
  • Client ReferralsNew
  • Execution of SEO
  • Reporting Tool

Resouce

  • Google Search Console
  • Google Keyword Planner
  • Google OptimiseHot
  • SEO Spider

Get the Top 10 in Search!

Looking for a trustworthy service to optimize the company website?
Request a Quote
Welcome Back!

Sign in to your account

Username or Email Address
Password

Lost your password?