
Eloquent writes created_at and updated_at on every insert: the error means the table does not have them. Three fixes, depending on the case: add them with a migration using $table->timestamps(), turn them off with public $timestamps = false; on the model, or declare their real names with the CREATED_AT and UPDATED_AT constants.
The message shows up on the first save() or create() on a model:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'INSERT INTO'The cause is always the same: Eloquent maintains two date columns, created_at and updated_at, and writes them on every insert or update. If the table does not have them, the query fails. There are three ways to fix this, and the right one depends on the table, not on your preferences.
Part of the Web development reading path. Error reproduced and fixes verified on Laravel 13.30.1 with MariaDB 11.8.9.
Recognising the error
The wording varies with the engine and the operation, but it always points at updated_at or created_at:
-- à l'insertion (MySQL / MariaDB)
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'INSERT INTO'
-- à la mise à jour
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'reports.updated_at' in 'SET'
-- sur SQLite
SQLSTATE[HY000]: General error: 1 table reports has no column named updated_atStart by checking what the table actually contains:
php artisan tinker --execute='echo implode(", ", Schema::getColumnListing("reports"));'id, titleIf created_at and updated_at are missing, read on. If they are there under another name, go straight to the third solution.
Solution 1: add the columns
The full procedure is described in the guide to Laravel migrations.
This is the right answer in most cases. Knowing when a row was created and last changed always pays off sooner or later, for debugging as much as for sorting. The timestamps() helper creates both columns at once, nullable:
php artisan make:migration add_timestamps_to_reports_table --table=reportspublic function up(): void
{
Schema::table('reports', function (Blueprint $table) {
$table->timestamps();
});
}
public function down(): void
{
Schema::table('reports', function (Blueprint $table) {
$table->dropTimestamps();
});
}php artisan migrateAfter the migration, the insert goes through and the dates are filled in. Verified: the table does hold id, title, created_at, updated_at and $report->created_at returns an Illuminate\Support\Carbon instance, which you can format and compare.
On a table that already holds rows, the columns are created as NULL on the existing data, which causes no trouble at all. If you want a value there, set it explicitly:
$table->timestamp('created_at')->nullable()->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrent()->useCurrentOnUpdate();Solution 2: turn timestamps off on the model
When the table is not meant to carry these dates, a pivot table, a frozen reference table or a schema imposed by a third-party system, say so on the model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Video extends Model
{
public $timestamps = false;
}Eloquent then stops writing the two columns, and the insert works. You can confirm the state with $video->usesTimestamps(), which returns false.
The same switch can be flipped on a single instance, for example to correct a value without changing updated_at:
$task->timestamps = false;
$task->title = 'Titre corrigé';
$task->save(); // updated_at stays unchangedIf the table ought to have these columns, $timestamps = false fixes nothing: it removes information you will go looking for later. Keep this solution for tables where a modification date genuinely makes no sense.
Solution 3: the columns exist under another name
On a legacy database, the columns are often called date_creation, creation_date or modified_on. There is no need to rename anything: give Eloquent the real names with two constants.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Clip extends Model
{
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'updated_date';
}Eloquent now writes to those columns. Verified on Laravel 13: after Clip::create(['title' => 'Extrait']), the creation_date column does hold the timestamp, and getUpdatedAtColumn() returns updated_date.
If only one of the two dates exists, set the other one to null:
const CREATED_AT = 'creation_date';
const UPDATED_AT = null; // no update columnThe $clip->created_at and $clip->updated_at accessors, however, do not follow these constants: they return null. Read the column by its real name, $clip->creation_date, or generically with $clip->{$clip->getCreatedAtColumn()}.
Which solution to pick
| Situation | Solution |
|---|---|
| Ordinary application table, columns simply forgotten | Add the columns with a migration |
| Pivot table, frozen reference table, imposed schema | public $timestamps = false; |
| Legacy database with date columns named differently | CREATED_AT and UPDATED_AT constants |
One write must not touch updated_at | $model->timestamps = false; before save() |
Common errors
$timestamps = false removes useful information instead of fixing the schema. Add the columns instead.timestamps() creates them nullable, which works. A NOT NULL definition with no default value would make the migration fail.

