How to soft delete related records when soft deleting a parent record in Laravel?

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company
# How to Soft Delete Related Records When Soft Deleting a Parent Record in Laravel As developers working with relational data in Laravel, managing cascading deletions—especially when utilizing soft deletes—presents a common architectural challenge. You have successfully implemented soft deletion on your `Invoice` model, but you encounter the classic problem: the related `Payment` records remain orphaned, violating the conceptual integrity of the parent record. This comprehensive guide details the correct, idiomatic Laravel approach to ensure that when an invoice is soft-deleted, all associated payments are also appropriately soft-deleted, maintaining data consistency across your application. ## The Soft Deletes Dilemma Let's review the structure you provided: **Invoice:** ```sql id | name | amount | deleted_at 2 iMac 1500 | NULL ``` **Payments:** ```sql id | invoice_id | amount | deleted_at 2 2 1000 | NULL ``` When you execute `$record->delete()` on the `Invoice` model, Laravel correctly sets the `deleted_at` timestamp on the invoice. However, since the `Payment` records do not have an explicit instruction to follow this deletion, they remain untouched and visible in the database. This is because soft deletes are primarily an application-level feature managed by Eloquent, not automatically enforced by standard SQL foreign key constraints alone. The solution lies in explicitly defining how these related models should behave when their parent is deleted. We need to leverage Eloquent relationships to enforce this cascading behavior. ## The Solution: Implementing Cascading Soft Deletes via Relationships Instead of relying on direct deletion calls, we must integrate the soft delete logic directly into our Eloquent relationships. This ensures that any query involving the parent automatically handles the related records. ### Step 1: Define the Relationship First, ensure you have a proper relationship defined between `Invoice` and `Payment`. In your `Invoice` model: ```php class Invoice extends Model { use SoftDeletes; public function payments() { // This defines the relationship back to the Payments table return $this->hasMany(Payment::class); } } ``` And in your `Payment` model: ```php class Payment extends Model { use SoftDeletes; public function invoice() { // This defines the relationship back to the Invoice table return $this->belongsTo(Invoice::class); } } ``` ### Step 2: Enforcing Cascading Deletion (The Crucial Step) While Eloquent doesn't offer a built-in `cascadeSoftDeletes()` method, we can enforce this logic within the deletion method itself. The most robust way to handle this is to use database constraints or, more commonly in Laravel, perform the related deletions explicitly within a transaction. For soft deletes, performing explicit deletion on the children ensures that the application state remains consistent, aligning with best practices discussed by developers focusing on data integrity in frameworks like Laravel. Here is how you modify your invoice deletion method: ```php use Illuminate\Support\Facades\DB; public function cancel(Request $request, $id) { // 1. Find the parent record $invoice = Invoice::with('payments')->findOrFail($id); // 2. Start a transaction for atomicity DB::beginTransaction(); try { // 3. Soft delete the parent invoice $invoice->delete(); // 4. Soft delete all related payments $invoice->payments()->whereNull('deleted_at')->update(['deleted_at' => now()]); DB::commit(); return response()->json(['success' => 'Invoice and related payments soft-deleted successfully.']); } catch (\Exception $e) { DB::rollBack(); return response()->json(['error' => 'Deletion failed: ' . $e->getMessage()], 500); } } ``` ### Explanation of the Approach By using `DB::beginTransaction()`, we ensure that either both the invoice and all its payments are deleted, or none of them are. 1. **Eager Loading:** We use `Invoice::with('payments')` to load the related payments efficiently before deletion, although this is primarily for context here. 2. **Parent Deletion:** `$invoice->delete()` handles the soft delete on the parent. 3. **Child Deletion:** We explicitly query and update all related payments using the defined relationship (`$invoice->payments()->whereNull('deleted_at')->update(...)`). This targets only those payments that have *not yet* been soft-deleted, ensuring we correctly cascade the operation. This method provides full control over the data flow, which is often necessary when implementing complex business rules in Laravel. For deeper dives into Eloquent relationships and database interactions, exploring resources on the official Laravel documentation can be extremely helpful. ## Conclusion Soft deleting related records is not an automatic feature of Eloquent; it requires careful orchestration of application logic. By defining clear Eloquent relationships and manually executing cascading soft deletes within a transaction, you gain complete control over your data integrity. This approach ensures that as you manage data in Laravel, you adhere to the principle of atomic operations, leading to more reliable and maintainable applications.