Read this first: how to decode a Laravel migration SQLSTATE error
If you’ve ever run php artisan migrate and been greeted by a wall of red text starting with SQLSTATE[...], this guide is for you. A Laravel migration SQLSTATE error is just MySQL (or MariaDB/Postgres) telling you exactly what went wrong — the code in brackets is the key. Learn to read that code and you can fix most migration failures in minutes instead of Stack Overflow rabbit holes.
Last verified: October 5, 2026, against Laravel 12/13 docs, MySQL 8.x behaviour, and current community fixes.
Below is a quick-reference table of the errors this guide covers, then a full fix for each one.
| Error code | Short meaning | Jump to fix |
|---|---|---|
SQLSTATE[42S01] |
Base table or view already exists | Fix 1 |
SQLSTATE[42S02] |
Base table or view not found (1146) | Fix 2 |
SQLSTATE[42S22] |
Column not found (1054 Unknown column) | Fix 3 |
SQLSTATE[23000] |
Integrity constraint violation (1062 Duplicate entry) | Fix 4 |
SQLSTATE[HY000] / errno 150 |
Foreign key constraint is incorrectly formed | Fix 5 |
SQLSTATE[42000] / 1071 |
Specified key was too long | Fix 6 |
SQLSTATE[HY000] [2002] |
Connection refused / No such file or directory | Fix 7 |
SQLSTATE[HY000] / 1067 |
Invalid default value (strict mode) | Fix 8 |
| No SQLSTATE at all | “Nothing to migrate” but tables are missing | Fix 9 |
1. SQLSTATE[42S01]: Base table or view already exists
What it looks like
SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'orders' already exists
(SQL: create table `orders` (...))
Why it happens
You ran php artisan migrate, one migration crashed halfway through, and some tables got created before the crash. The failed migration was never recorded in the migrations table, so the next run tries to create the table again — and MySQL refuses because it’s already there. This is the single most common cause of “my migration worked once and now it’s broken”.
The fix
First, check which migrations Laravel thinks have run:
php artisan migrate:status
Then open your database client and compare against the actual tables. You have three options, from safest to most destructive:
- Local dev, data you don’t care about: drop the half-created tables manually (or the whole database) and run
php artisan migrateagain from a clean slate. - Local dev, but you want to keep data:
php artisan migrate:rollbackthe batch that contains the broken migration, fix the migration file, and migrate again. - Nuclear option (dev only):
php artisan migrate:freshdrops every table and re-runs everything. Never run this on production or a shared database — the Laravel docs warn about this explicitly.
In production, never drop tables. Instead, manually insert the missing row into the migrations table for migrations whose schema already exists, or write a small corrective migration that uses Schema::hasTable() guards.
2. SQLSTATE[42S02]: Base table or view not found (1146)
What it looks like
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'shop.products' doesn't exist
(SQL: alter table `products` add ...)
Why it happens
Somewhere you called Schema::table() (modify) on a table that was never created. The usual culprits: a migration that runs before the migration that creates the table (migration order comes from the timestamp in the filename), a copy-pasted down() method that drops the wrong table so a rollback deleted something it shouldn’t have, or a migration that failed silently earlier in the batch.
The fix
- Confirm the table really is missing with
php artisan migrate:statusand a look in your DB client. - Check the timestamps on your migration filenames — the parent (create) migration must have an earlier timestamp than any migration that alters the table. If you created them out of order, rename the files so the order is correct.
- Make sure you’re using
Schema::createfor new tables andSchema::tableonly for modifications — mixing them up is the classic copy-paste bug. - If a bad
down()dropped the table, recreate it from the original create migration, then re-run the alter migrations.
3. SQLSTATE[42S22]: Column not found (1054 Unknown column)
What it looks like
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'full_name' in 'field list'
(SQL: select `full_name` from `users` ...)
Why it happens
Your code references a column that doesn’t exist in the database. This one usually isn’t a migration bug at all — it’s a mismatch between your code and your schema. You renamed a column in a migration but some query, Eloquent relationship, or $fillable entry still uses the old name, or you simply forgot to run php artisan migrate after pulling new code.
The fix
- Run
php artisan migrate— seriously, start here. Half of these reports end here. - Grep your codebase for the old column name. A rename in a migration doesn’t rename it in your models, controllers, or raw queries.
- Check Eloquent relationships: if a
belongsTodoesn’t follow Laravel’smodel_idconvention, you must pass the foreign key name explicitly. - If you switched databases or branches recently, run
php artisan config:clear— a cached config can point you at a stale database. - Verify
$fillable,$casts, and anyselect()clauses match the actual columns (check with your DB client, not your memory).
4. SQLSTATE[23000]: Integrity constraint violation (1062 Duplicate entry)
What it looks like
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
'admin@example.com' for key 'users_email_unique'
Why it happens
You’re inserting a row that violates a unique index or a foreign key. In migrations this almost always comes from seeders, not the migration itself: you ran php artisan db:seed twice, or your seeder uses create() instead of an idempotent method. It can also appear when a migration backfills a column with a default value that collides with the unique index.
The fix
- Make seeders idempotent — use
updateOrCreate()orfirstOrCreate()instead ofcreate()so re-running the seeder is safe:
User::updateOrCreate(
['email' => 'admin@example.com'],
['name' => 'Admin', 'password' => Hash::make('secret')],
);
- In dev, if the data is disposable:
php artisan migrate:fresh --seedand move on. - If duplicates already exist in a table you can’t wipe, find them with
GROUP BY ... HAVING COUNT(*) > 1, clean them up, then add the unique index in a separate migration.
5. SQLSTATE[HY000]: General error 1005 — errno 150 “Foreign key constraint is incorrectly formed”
What it looks like
SQLSTATE[HY000]: General error: 1005 Can't create table `shop`.`orders`
(errno: 150 "Foreign key constraint is incorrectly formed")
(SQL: alter table `orders` add constraint `orders_user_id_foreign`
foreign key (`user_id`) references `users` (`id`))
Why it happens
MySQL is picky about foreign keys, and errno 150 means the two sides don’t match. Check these in order:
- Column types must be identical. If
users.idisbigIncrements(unsigned bigint), the foreign key must beunsignedBigInteger— a plainintegeror signedbigIntegerwill fail. This is the cause 90% of the time. - Migration order. The parent table’s migration must run before the child table’s. Check the timestamps in the filenames.
- Table name typos in
->on('users'). - Engine/charset mismatch — both tables must be InnoDB with the same charset.
- The referenced column must be indexed (a primary key or unique key).
The fix
The modern, typo-proof way is foreignId(), which creates the correctly-typed column and the constraint in one call:
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
constrained() infers the users table and the id column from the name, and the column type always matches $table->id(). If you’re fixing an existing broken migration, change the foreign key column to unsignedBigInteger to match a bigIncrements/id() primary key.
6. SQLSTATE[42000]: 1071 Specified key was too long
What it looks like
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long;
max key length is 767 bytes
(SQL: alter table `users` add unique `users_email_unique`(`email`))
Why it happens
Laravel uses utf8mb4 by default, where each character can take 4 bytes. A string column defaults to 255 characters — that’s up to 1020 bytes, which exceeds the 767-byte index limit on MySQL older than 5.7.7 and MariaDB older than 10.2.2. Newer database versions raised the limit, so if you’re hitting this, your database server is old.
The fix
You have two good options. The Laravel-documented one is to cap the default string length in app/Providers/AppServiceProvider.php:
use Illuminate\Support\Facades\Schema;
public function boot(): void
{
Schema::defaultStringLength(191);
}
191 characters × 4 bytes = 764 bytes, which fits under the 767-byte limit. Alternatively, upgrade MySQL to 5.7.7+ or MariaDB to 10.2.2+, where the limit was raised and the problem disappears entirely. You can also set an explicit shorter length on individual indexed columns ($table->string('email', 100)->unique()), but the provider-level fix covers every migration at once.
7. SQLSTATE[HY000] [2002]: Connection refused / No such file or directory
What it looks like
SQLSTATE[HY000] [2002] No such file or directory
(SQL: select * from information_schema.tables where table_schema = ...)
or SQLSTATE[HY000] [2002] Connection refused.
Why it happens
Laravel can’t reach MySQL at all — the migration never even starts. “No such file or directory” means PHP tried to connect via a Unix socket that doesn’t exist; “Connection refused” means nothing is listening on the TCP host/port. The classic trigger: DB_HOST=localhost makes PHP use a socket, and the socket path PHP expects doesn’t match where MySQL actually created it.
The fix
- Switch
DB_HOSTfromlocalhostto127.0.0.1in your.env. This forces a TCP connection and sidesteps socket-path mismatches entirely. - Confirm MySQL is actually running (
sudo systemctl status mysqlorbrew services list). - Double-check
DB_PORT,DB_DATABASE,DB_USERNAME, andDB_PASSWORD. - After any
.envchange, runphp artisan config:clear— a cached config will keep using the old values and you’ll swear the fix “didn’t work”.
8. SQLSTATE[HY000]: 1067 Invalid default value (MySQL strict mode)
What it looks like
SQLSTATE[HY000]: General error: 1067 Invalid default value for 'published_at'
(SQL: create table `posts` (... `published_at` timestamp not null ...))
Why it happens
Laravel enables MySQL strict mode by default ('strict' => true in config/database.php), and strict mode rejects zero dates and NOT NULL timestamp columns with no default. A bare $table->timestamp('published_at') generates exactly that.
The fix
Give the column an explicit default or make it nullable:
$table->timestamp('published_at')->nullable();
// or
$table->timestamp('published_at')->useCurrent();
Don’t “fix” this by turning strict mode off — strict mode is protecting you from silent data truncation elsewhere. Fix the column definition instead.
9. “Nothing to migrate” — but your tables are missing
What it looks like
INFO Nothing to migrate.
…while the table you’re querying doesn’t exist.
Why it happens
Laravel decides what to run by comparing migration filenames against rows in the migrations table. If that table says a migration ran but the actual table is gone (someone dropped it manually, a bad down(), a restored backup that included the migrations table but not your tables), Laravel sees nothing to do. A related gotcha: calling ->change() on a column without doctrine/dbal installed throws “Changing columns requires Doctrine DBAL” — fix it with composer require doctrine/dbal.
The fix
- Run
php artisan migrate:statusto see what Laravel believes. - Open the
migrationstable in your DB client. For any migration whose table is actually missing, delete that row — the nextphp artisan migratewill pick it up and run it. - On a fresh clone or new environment, prefer
php artisan migrate:freshover debugging a hand-editedmigrationstable.
How to stop Laravel migration SQLSTATE errors before they start
- Always write a working
down()method. Most 42S01/42S02 nightmares trace back to rollbacks that didn’t reverse cleanly. - Test migrations with
migrate:freshlocally before pushing — if a fresh build fails on your machine, it will fail in CI and production too. - One change per migration. Small migrations are easier to debug, roll back, and reorder.
- Never edit a migration that has already run in production. Write a new migration instead. Editing history is how 42S01 happens on deploy.
- Use
foreignId()->constrained()for every foreign key — it eliminates the entire errno 150 class of bugs. - Run
php artisan migrate:statuswhen anything looks off. It takes two seconds and answers “what does Laravel think has run?” - Keep
.envandconfig:clearin your mental checklist for every connection-related error.
Further Reading & References
- Laravel official documentation: Database Migrations — the authoritative reference for migrate, rollback, fresh, status, and the schema builder.
- Fixing errno 150 “Foreign key constraint is incorrectly formed” on Laravel migration (mazer.dev) — walks through the unsignedBigInteger mismatch and migration ordering.
- Base Table Or View Already Exists in Laravel Migration (scratchcode.io) — covers the 42S01 fixes: migrate:fresh, rollback, and re-migrating.
- How to Fix “SQLSTATE[42S22]: Column Not Found” in Laravel 12 (jonathanbird.com.au) — a thorough 2026 walkthrough of the 1054 unknown-column error.
- SQLSTATE[42S02]: Base table or view not found: 1146 (laravel.io forum) — community thread confirming the Schema::table vs Schema::create mix-up.
- Laravel SQLSTATE[42S22]: Column Not Found (1054) — Unknown Column Solution (parthfaladu.com) — checklist-style fix for the 42S22 error.
- Fixing Laravel’s SQLSTATE[HY000] [2002] No such file or directory Error (YouTube) — video walkthrough of the DB_HOST localhost vs 127.0.0.1 socket fix.
- Laravel Migration Operations and Common Errors (medianova.com) — covers the Doctrine DBAL requirement for ->change() and other migration pitfalls.



