Laravel Validation with Soft Deleted Model and Unique DB field

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Uniqueness: Laravel Validation with Soft Deleted Models

Dealing with unique constraints in a system that utilizes soft deletes introduces a layer of complexity. When you combine Eloquent's soft delete feature with database-level uniqueness rules, handling updates, creations, and deletions requires careful thought. As a senior developer, we need solutions that are not only functional but also robust, scalable, and respect the state of the data (deleted vs. active).

This post will walk you through structuring a solution for managing unique fields (like email addresses) on models that use soft deletes in Laravel, covering the tricky scenarios you outlined.

The Challenge: Soft Deletes and Uniqueness

When a model uses soft deletes (->softDeletes()), Eloquent queries naturally exclude records where deleted_at is not null. However, database-level unique constraints might still apply to all rows, regardless of the soft-delete status, unless explicitly filtered in every query.

The core difficulty lies in determining if an existing record blocks a new action:

  1. Does simply exist block the action? (Yes)
  2. Does merely exist and be soft-deleted block the action? (No, usually)

To solve this elegantly, we need to customize how we check for existence during validation or before saving.

Strategy: Conditional Existence Checks

The most effective strategy here is to write custom logic that explicitly tells Eloquent to only look at active records when checking for duplicates. This ensures that soft-deleted emails do not block legitimate new entries.

Handling the Three Scenarios

Let's address your specific use cases by focusing on how we query the database:

1. Updating the Same Model (Ignoring Change)

If a user attempts to update their own record, and the email field hasn't changed, we should bypass the uniqueness check entirely. This is handled best within the controller logic before model saving, rather than purely in the validation rules, as validation rules are generally static checks.

2. Updating a Different Model (Email Collision)

When updating User A to have an email already held by User B, this is a genuine collision that should be blocked. This requires a standard check against active records.

3. Creating a New Model (Email Already Exists)

When creating a new record, if the email already exists on an active model, we must block the creation.

Implementing Robust Uniqueness Checks

Instead of relying solely on built-in Eloquent rules, which can become overly complex when dealing with soft deletes and conditional logic, we can implement a custom scope or a dedicated service layer to manage this query logic cleanly.

Here is how you might structure the uniqueness check within your model or a dedicated repository:

// Example in your User Model (or a Repository class)

public function hasUniqueEmail(string $email, ?int $userId = null): bool
{
    $query = $this->where('email', $email);

    // Scenario 1: Check if the email exists on any *active* user EXCEPT the current model being updated (if $userId is provided).
    if ($userId) {
        $query->where('id', '!=', $userId);
    }

    // Crucially, we only check against non-deleted records.
    $exists = $query->whereNull('deleted_at')->exists();

    return !$exists;
}

When applying this logic during the save process (e.g., in a service layer or controller):

// In your controller logic before saving:
if (! $user->hasUniqueEmail($user->email, $user->id)) {
    // Throw an error indicating a duplicate email exists among active users.
    throw new \Exception("This email is already in use.");
}
$user->save();

Conclusion and Best Practices

Handling uniqueness with soft deletes requires moving the logic slightly outside of simple framework validation rules and into custom query methods that explicitly respect the deleted_at column. This separation ensures that your data integrity checks are explicit and predictable, which is a hallmark of solid application architecture, aligning perfectly with the principles taught at places like Laravel Company.

By implementing conditional existence checks based on active records, you successfully manage all three scenarios: ignoring non-changed updates (Scenario 1), blocking collisions during updates (Scenario 2), and preventing duplicate creations (Scenario 3), all while respecting the soft-deleted state of your database entries. Always favor clear, explicit query logic over relying solely on implicit model behavior when dealing with complex data states.