logo

A subtle gotcha with Eloquent's date cast

What you read isn't always what gets stored

Joel Clermont
Joel Clermont
2026-03-19

Eloquent's date cast gives you a Carbon instance with the time zeroed out. If your set a model value of 2023-05-15 14:30:45, the cast returns 2023-05-15 00:00:00.

That feels safe. You asked for a date, you got a date.

But the cast only strips time on read. On write, the full time data still gets sent to the database.

// in the model
protected function casts(): array
{
    return [
        'start_date' => 'date',
    ];
}
// now using that model
$record->start_date = '2023-05-15 14:30:45';
$record->start_date; // 2023-05-15 00:00:00

$record->save();

If your model stores start_date in a DATE column, this all works as expected. MySQL strips can't store the time in a DATE column, so the stored value is just 2023-05-15.

But if start_date is a DATETIME column, this is where things get weird. MySQL will actually store the full value with the time.

// going direct through the DB facade, and not Eloquent, we can see it
DB::table('events')->where('id', $record->id)->first()->start_date;
// 2023-05-15 14:30:45

Eloquent told you the time was midnight, but the database kept 14:30:45.

This can bite you in raw queries, reports, or anything outside Eloquent. A query like WHERE start_date > '2023-05-15 12:00:00' will match this record, even though Eloquent insisted the time was 00:00:00.

The takeaway is to set the correct schema for your columns and not rely on casts to paper over differences. If a field is truly date-only, make the column a DATE.

Casts control how Eloquent reads data, but your schema is what enforces how it's stored.

Here to help,

Joel

P.S. This type of subtle logic surprise is a great example of why tests are so important. Learn how to write tests that catch these kinds of issues in our testing workshop.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼
email
No spam. Only real-world advice.