Welcome to HowToShipIt — practical how-to guides for developers: code, AI tools, and servers, explained step by step.

How to Fix Laravel Migration SQLSTATE Errors: 9 Fixes That Work

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 migrate again from a clean slate.
  • Local dev, but you want to keep data: php artisan migrate:rollback the batch that contains the broken migration, fix the migration file, and migrate again.
  • Nuclear option (dev only): php artisan migrate:fresh drops 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

  1. Confirm the table really is missing with php artisan migrate:status and a look in your DB client.
  2. 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.
  3. Make sure you’re using Schema::create for new tables and Schema::table only for modifications — mixing them up is the classic copy-paste bug.
  4. 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

  1. Run php artisan migrate — seriously, start here. Half of these reports end here.
  2. Grep your codebase for the old column name. A rename in a migration doesn’t rename it in your models, controllers, or raw queries.
  3. Check Eloquent relationships: if a belongsTo doesn’t follow Laravel’s model_id convention, you must pass the foreign key name explicitly.
  4. If you switched databases or branches recently, run php artisan config:clear — a cached config can point you at a stale database.
  5. Verify $fillable, $casts, and any select() 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() or firstOrCreate() instead of create() 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 --seed and 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:

  1. Column types must be identical. If users.id is bigIncrements (unsigned bigint), the foreign key must be unsignedBigInteger — a plain integer or signed bigInteger will fail. This is the cause 90% of the time.
  2. Migration order. The parent table’s migration must run before the child table’s. Check the timestamps in the filenames.
  3. Table name typos in ->on('users').
  4. Engine/charset mismatch — both tables must be InnoDB with the same charset.
  5. 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

  1. Switch DB_HOST from localhost to 127.0.0.1 in your .env. This forces a TCP connection and sidesteps socket-path mismatches entirely.
  2. Confirm MySQL is actually running (sudo systemctl status mysql or brew services list).
  3. Double-check DB_PORT, DB_DATABASE, DB_USERNAME, and DB_PASSWORD.
  4. After any .env change, run php 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

  1. Run php artisan migrate:status to see what Laravel believes.
  2. Open the migrations table in your DB client. For any migration whose table is actually missing, delete that row — the next php artisan migrate will pick it up and run it.
  3. On a fresh clone or new environment, prefer php artisan migrate:fresh over debugging a hand-edited migrations table.

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:fresh locally 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:status when anything looks off. It takes two seconds and answers “what does Laravel think has run?”
  • Keep .env and config:clear in your mental checklist for every connection-related error.

Further Reading & References

Leave a Comment