Skip to content

Laravel Eloquent Timestamps: Control created_at & updated_at

How Laravel Eloquent timestamps work: disable them, rename columns, use Unix time, and update a model without changing updated_at. Tested examples.

Dasun Kalupahana 3 min read 76 views 0 comments
On this page
  1. The default behavior
  2. Turn timestamps off
  3. Custom column names
  4. Store them as Unix timestamps
  5. Update a record without touching updated_at
  6. Where timestamps do not happen
  7. Quick checklist
  8. References

Every Laravel model quietly fills in created_at and updated_at, and most of the time that is exactly what you want. The trouble starts when a counter bumps updated_at on every page view, a bulk insert leaves the columns empty, or a legacy table uses different column names. This article covers how Eloquent timestamps work, how to change them, and the places they silently do not run. Everything below was run against the real Laravel 13.35.0 Eloquent classes and MariaDB.

The default behavior

The migration helper $table->timestamps() adds both columns. Eloquent then sets them for you:

Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->unsignedInteger('reads')->default(0);
    $table->timestamps();
});

$post = Post::create(['title' => 'Hello']);
// created_at = 10:00:00, updated_at = 10:00:00

Two details that surprise people, both confirmed in my test with a frozen clock:

  • $post->save() with nothing changed does not touch updated_at. No changes means no UPDATE query at all.
  • Changing a field and saving moves only updated_at (10:00 became 10:05). created_at never changes.

Turn timestamps off

For tables like logs or pivots that have no such columns, tell Eloquent to stop. The current documentation uses PHP attributes:

use Illuminate\Database\Eloquent\Attributes\WithoutTimestamps;
use Illuminate\Database\Eloquent\Model;

#[WithoutTimestamps]
class LogEntry extends Model
{
    // ...
}

The same thing is possible with #[Table(timestamps: false)]. I checked that both attributes make usesTimestamps() return false, and the insert ran without trying to write the missing columns. The older public $timestamps = false; property is still read by the framework source, so existing code keeps working, but the attribute form is what the docs show now.

Custom column names

Working with an old database that calls them creation_date and updated_date? Define two constants:

class Flight extends Model
{
    public const CREATED_AT = 'creation_date';
    public const UPDATED_AT = 'updated_date';
}

Both columns were filled correctly in my run.

Store them as Unix timestamps

By default dates are stored in your database's standard date format. To store a different format, set the date format on the model. The docs show 'U', which is a Unix timestamp:

use Illuminate\Database\Eloquent\Attributes\DateFormat;

#[DateFormat('U')]
class Flight extends Model
{
    // ...
}

#[Table(dateFormat: 'U')] does the same. With an integer column the row stored 1767261600, and reading $flight->created_at still returned a Carbon object. Remember that the column must be able to hold that format, so I used an unsigned integer column for this test.

Update a record without touching updated_at

This is the one most people need. Think of a view counter: you do not want an article to look "recently edited" just because someone read it. The docs give this exact tool:

use Illuminate\Database\Eloquent\Model;

Model::withoutTimestamps(fn () => $post->increment('reads'));

Results from my test, with the clock moved forward before each step:

OperationDid updated_at change?
$post->save(), nothing changedNo
$post->save(), field changedYes
$post->touch()Yes
$post->increment('reads')Yes
withoutTimestamps(fn () => $post->increment('reads'))No
withoutTimestamps around a save() with changesNo
Post::where(...)->update([...])Yes
saveQuietly()Yes

Note the last row. saveQuietly() only mutes model events. It does not skip timestamps, which is a common mix-up. If you need both, wrap it in withoutTimestamps too. Also notice that increment() bumps updated_at by default, so a plain view counter will keep refreshing it unless you wrap it.

Where timestamps do not happen

Eloquent sets timestamps when it saves a model object or runs an Eloquent query builder update. Anything that skips the model skips them too. In my test:

  • DB::table('posts')->where(...)->update([...]) changed the title but left updated_at alone.
  • DB::table('posts')->insert([...]) produced a row with created_at and updated_at both NULL.
  • Post::insert([...]) (bulk insert) did the same. insert() never creates model instances, so nobody sets the dates.

If you bulk insert, add the dates yourself with now() on each row, otherwise reports that filter on created_at will silently miss them. This is the same trade-off as in the earlier query optimization article: bulk operations are fast because they skip the model layer, and the model layer is also what fills in your timestamps.

Quick checklist

  • No created_at/updated_at columns? Use #[WithoutTimestamps].
  • Different column names? CREATED_AT and UPDATED_AT constants.
  • Counter or background sync should not look like an edit? Model::withoutTimestamps().
  • Using insert() or DB::table()? Set the dates yourself.
  • Want to bump updated_at only, for cache busting? $model->touch().

References

Free resources for this topic

Code snippets and fixes for this topic

Get one practical tip every week

New tutorials, fixes and free cheat sheets from DEV Talk, straight to your inbox. No spam, unsubscribe in one click.

Free download: Laravel interview questions (PDF)

Have a question about this post?

Ask the community: other developers and the author can answer.

Ask the community

Get notified about new articles

One click, no email needed. Turn it off anytime with the bell at the top.

Get new articles on your iPhone

Add DEV Talk to your Home Screen, open it, then tap the bell.

Rate this post
0.0
Share

Enjoying the content?

If this article helped you, you can support the author with a coffee. It keeps new posts coming.

Buy me a coffee (opens in a new tab)

Comments 0

Be kind. Comments with links are reviewed before they appear.

No comments yet

Be the first to share your thoughts.

Get new articles in your inbox

Practical tutorials and deep dives, about once a week. No spam, unsubscribe anytime.

Privacy policy

Add DEV Talk to your Home Screen

  1. 1 Tap the Share button in Safari's toolbar.
  2. 2 Scroll down and tap Add to Home Screen.
  3. 3 Tap Add. Open it from the Home Screen to read like an app and to turn on notifications with the bell.

We use cookies to improve your experience and analyze traffic. By continuing, you agree to our Privacy Policy.