Laravel Eloquent, how to handle UNIQUE error?

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Laravel Eloquent: Mastering Unique Constraints and Error Handling

As developers working with relational databases through an ORM like Laravel Eloquent, we frequently encounter integrity constraint violations—the dreaded UNIQUE errors. This is especially common when dealing with composite keys or unique indexes defined at the database level, which ensures data integrity regardless of how the application interacts with it.

The scenario you presented—attempting to save a model that violates a unique constraint—is a classic challenge. While manually checking before saving (as shown in your example) works, it often introduces race conditions and is less performant than letting the database handle the conflict directly.

Let’s dive into why this happens and explore the most robust, idiomatic ways to manage unique constraints in a Laravel application.

Understanding the Root Cause

When you execute $foo->save(), Eloquent translates this into an INSERT statement against the MySQL database. If the underlying table has a UNIQUE constraint on columns (foo, bar), and a record with that combination already exists, the database immediately rejects the transaction, throwing an SQLSTATE error (like 1062 Duplicate entry '42' for key 'Unique').

The issue isn't necessarily how Eloquent finds the duplicate; it’s what happens when the database enforces its rules during the write operation. Your proposed solution of checking first (if (!Foo::where(...)->first())) is a valid application-layer defense, but it shifts the burden from the database (which is optimized for this) to your PHP code, potentially leading to race conditions in high-concurrency environments.

The Best Practice: Handling Exceptions Gracefully

Instead of trying to preemptively block the error with complex queries, the most reliable approach is to let the database signal the failure and handle that specific exception within your application logic. This aligns with Laravel’s philosophy of building robust applications by correctly interpreting external system feedback.

We can leverage Laravel's exception handling mechanisms to catch these specific database errors gracefully. When interacting with the database via Eloquent, we often wrap save operations in a try...catch block.

Here is how you can implement this safely:

use Illuminate\Database\QueryException;
use App\Models\Foo;

try {
    $foo = new Foo(['foo' => 42, 'bar' => 1]);
    $foo->save();
} catch (QueryException $e) {
    // Check if the error is specifically a unique constraint violation (MySQL error code 1062)
    if ($e->getCode() === '23000') { // SQLSTATE for integrity constraint violation
        // Handle the duplicate entry scenario
        \Log::warning("Duplicate entry detected during save: " . $e->getMessage());

        // Decide what to do next: throw a custom exception, return an error response, etc.
        throw new \Exception("A record with these unique values already exists.", 409);
    }
    // Re-throw any other unexpected database errors
    throw $e;
}

Advanced Technique: Using Database Transactions

For operations that require multiple steps (checking existence and then inserting), the safest method is to wrap the entire operation within a database transaction. This ensures atomicity—either all steps succeed, or none of them do.

If you are performing checks before writes, wrapping it in a transaction guarantees that if the insert fails due to integrity constraints, no partial data is committed. This practice is fundamental when dealing with complex data manipulations, as advocated by modern Laravel development practices found on resources like laravelcompany.com.

use Illuminate\Support\Facades\DB;

try {
    DB::beginTransaction();

    // 1. Check for existence (Optional pre-check)
    if (Foo::where('foo', 42)->where('bar', 1)->exists()) {
        throw new \Exception("Record already exists.");
    }

    // 2. Insert the record
    $foo = new Foo(['foo' => 42, 'bar' => 1]);
    $foo->save();

    DB::commit();
    return $foo;

} catch (\Exception $e) {
    DB::rollBack(); // Rollback all changes if any error occurs
    // Log the specific error details here
    throw $e;
}

Conclusion: Prioritizing Database Integrity

The core takeaway is that when enforcing uniqueness, the database should be the primary gatekeeper. Relying solely on application-layer pre-checks can introduce fragility. The superior approach for handling unique constraint errors in Laravel Eloquent is to catch specific QueryExceptions and use database transactions to manage atomic operations. This keeps your code clean, leverages the power of the database engine efficiently, and ensures that your application remains reliable under heavy load.