
In this article
Most CakePHP Migrations 5 upgrade failures come from one change: Phinx is gone. Seeds now run with bin/cake seeds run and are tracked so they run only once, adapter queries return CakePHP statements, migration history can move from phinxlog to a single cake_migrations table, and unnamed foreign keys get new names. Fix those five areas and the upgrade is usually done.
Migrations 5.0.0 was released on 21 January 2026; the latest patch in October 2026 is 5.2.6 (29 July 2026). It requires PHP 8.2+ and CakePHP 5.3+, and the cakephp/app 5.4 skeleton now requires cakephp/migrations: ^5.0, so anyone creating or upgrading an app this year meets it. Below, each problem is listed with its symptom, cause and fix.
What changed at a glance
| Area | Migrations 4.x | Migrations 5.x |
|---|---|---|
| Backend | Phinx or builtin (builtin default since 4.4) | Builtin only; Phinx dependency removed |
| Run seeds | bin/cake migrations seed | bin/cake seeds run |
| Seed tracking | None; seeds re-run every time | Recorded in cake_seeds; run once by default |
| History table | phinxlog and {plugin}_phinxlog | cake_migrations for new apps; legacy tables auto-detected |
getAdapter()->query() | Phinx result | Cake\Database\StatementInterface |
| Unnamed foreign keys (MySQL) | Named by MySQL, e.g. articles_ibfk_1 | Named {table}_{columns} by the plugin |
| Requirements | PHP 8.1+ | PHP 8.2+, CakePHP 5.3+ |
Before changing anything, upgrade the dependency and confirm the app still boots:
composer require cakephp/migrations:^5.2
bin/cake migrations statusProblem 1: “migrations seed” is not a command
Symptom: deploy scripts or CI jobs that call bin/cake migrations seed fail after the upgrade. Cause: the Phinx wrapper commands were removed. Fix: switch to the new command group.
bin/cake seeds run # all seeds
bin/cake seeds run Articles # one seed (the "Seed" suffix is optional)
bin/cake seeds status # which seeds have run
bin/cake seeds reset # clear seed trackingIf you cannot update every script at once, the official guide shows an alias in Application::console():
public function console(CommandCollection $commands): CommandCollection
{
$commands = $this->addConsoleCommands($commands);
$commands->add('migrations seed', \Migrations\Command\SeedCommand::class);
return $commands;
}Problem 2: a seed says “already executed” and does nothing
Symptom: a development seed that used to refill data on every run is now skipped. Cause: seeds are tracked in the cake_seeds table and run once by default, which prevents accidental duplicates in production. Fix: choose deliberately.
- Re-run one seed on purpose:
bin/cake seeds run Users --force. - Make the run-once rule visible to the team:
bin/cake seeds status. - For reference data that should always be applied, override
isIdempotent()to returntrue, and make the seed safe to repeat.
The cleanest idempotent pattern uses insertOrSkip(), new in 5.x, which skips rows that would violate a unique constraint:
<?php
declare(strict_types=1);
use Migrations\BaseSeed;
class CurrenciesSeed extends BaseSeed
{
public function isIdempotent(): bool
{
return true;
}
public function run(): void
{
$this->table('currencies')
->insertOrSkip([
['code' => 'USD', 'name' => 'US Dollar'],
['code' => 'EUR', 'name' => 'Euro'],
['code' => 'INR', 'name' => 'Indian Rupee'],
])
->saveData();
}
}This only works if currencies.code has a unique index. Without one, every run inserts duplicates. Use getDependencies() (returning seed names such as ['Users']) when a seed needs other seeds first; dependencies that have not yet run are now executed automatically.

Problem 3: fetchAll() returns the wrong shape
Symptom: a data migration that reads rows breaks with undefined index notices or numeric keys. Cause: getAdapter()->query() now returns a CakePHP StatementInterface. Fix: ask for associative rows explicitly.
$stmt = $this->getAdapter()->query('SELECT id, email FROM users WHERE email_verified IS NULL');
foreach ($stmt->fetchAll('assoc') as $row) {
// $row['id'], $row['email']
}
$first = $this->getAdapter()->query('SELECT COUNT(*) AS n FROM users')->fetch('assoc');Problem 4: Class “AbstractMigration” not found
Symptom: old migration or seed files fatal on load. Cause: files that extend Phinx\Migration\AbstractMigration, Migrations\AbstractMigration or Migrations\AbstractSeed reference classes that no longer exist; the 5.x source only ships BaseMigration and BaseSeed. Fix: change the parent class. The table API is the same for common operations, so the method bodies rarely need edits.
<?php
declare(strict_types=1);
use Migrations\BaseMigration;
class AddStatusToOrders extends BaseMigration
{
public function change(): void
{
$this->table('orders')
->addColumn('status', 'string', ['limit' => 20, 'default' => 'pending', 'null' => false])
->addCheckConstraint('orders_status_valid', "status IN ('pending', 'paid', 'cancelled')")
->update();
}
}Find stragglers with grep -rn "AbstractMigration\|AbstractSeed\|Phinx" config/ plugins/. The check constraint above is another 5.x addition; MySQL enforces CHECK constraints from 8.0.16 onwards.
Problem 5: dropForeignKey() cannot find the constraint
Symptom: a rollback or a later migration fails when dropping a foreign key. Cause: when addForeignKey() is called without a name, 5.x now generates {table}_{columns} on every database. On MySQL the old migrations produced server-generated names such as articles_ibfk_1, so the names in your database and in new code may disagree. Fix: name constraints explicitly in new migrations, and drop old ones by the name that really exists.
SELECT CONSTRAINT_NAME, TABLE_NAME
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE();$this->table('comments')
->addForeignKey('article_id', 'articles', 'id', [
'delete' => 'CASCADE',
'update' => 'NO_ACTION',
'constraint' => 'comments_article_id_fk',
])
->update();Generated names are truncated to fit database limits (61 characters on MySQL) and get a numeric suffix on conflict.

Problem 6: two history tables, or migrations re-running
Symptom: you see both phinxlog and cake_migrations, or are unsure which one is in use. Cause: 5.x uses the unified cake_migrations table (with a plugin column) for new apps, but if any phinxlog table exists it keeps using the legacy tables. That is controlled by Migrations.legacyTables: null autodetects, false forces the new table, true forces legacy. Fix: migrate the history when you are ready, not during the dependency bump.
- Preview:
bin/cake migrations upgrade --dry-run. - Copy history:
bin/cake migrations upgrade. - Set
'Migrations' => ['legacyTables' => false]inconfig/app.phpand deploy. - Only after a few clean deploys, run
bin/cake migrations upgrade --drop-tables. This step cannot be rolled back.
Do this one environment at a time, starting with a copy of production data.
Problem 7: deploys continue with pending migrations
Migrations 5.2 can act as a deploy gate. bin/cake migrations status --all checks the app and every loaded plugin, prints a short summary and exits with 0 when everything is applied, 3 when migrations are pending and 2 when the history table lists migrations whose files are missing:
bin/cake migrations migrate
bin/cake migrations status --all || { echo "Schema not in sync"; exit 1; }MISSING entries usually mean someone deleted or renamed a migration file. bin/cake migrations status --cleanup removes them from history, but check why they vanished first.
A safe upgrade order
- On 4.x, confirm you are on the builtin backend (the default since 4.4) and that
bin/cake migrations statusis clean. - Bump to
cakephp/migrations:^5.2and fix the parent classes of migrations and seeds. - Replace
migrations seedin scripts, or add the alias. - Search data migrations for
fetchAll()andfetch()and add'assoc'. - Run the full migration set against an empty test database, then roll back one step and re-apply, to flush out foreign key naming issues.
- Later, as a separate change, move history to
cake_migrations.
Gotchas
- Tests use the same plugin. The skeleton’s
tests/bootstrap.phpbuilds the test schema with(new Migrator())->run(), so a broken migration breaks the whole test suite. - Seeds are not fixtures. Production seeds should be idempotent; demo data belongs in a seed you never run in production.
- MySQL online DDL. 5.x adds
algorithmandlockoptions on table updates (for exampleINPLACEandNONE); MySQL raises an error if it cannot honour the combination, which is better than an unexpected table lock.
Frequently asked questions
Does CakePHP Migrations 5 still use Phinx?
No. Phinx was removed as a dependency in 5.0; the builtin backend, built on CakePHP’s database layer, is the only backend.
How do I run seeds in Migrations 5?
Use
bin/cake seeds run, orbin/cake seeds run Namefor one seed.bin/cake migrations seedwas removed, though you can alias it inApplication::console().Why does my seed only run once?
Seed execution is now tracked in the
cake_seedstable. Use--force,bin/cake seeds reset, or make the seed idempotent by overridingisIdempotent().Do I have to migrate from phinxlog to cake_migrations?
No. If a
phinxlogtable exists, 5.x keeps using it. You can move later withbin/cake migrations upgradeand setMigrations.legacyTablestofalse.Which versions does Migrations 5 require?
PHP 8.2 or later and CakePHP 5.3 or later, according to the plugin’s upgrade guide and composer.json.
Can I use migration status as a CI check?
Yes.
bin/cake migrations status --allexits with 3 when migrations are pending and 2 when history entries have no matching file.
Your next customer could start right here.
Introduce your tool, product or business to readers exploring technology.
Explore placements and request a private quote
Sources
- Upgrading from 4.x to 5.x — CakePHP Book (Migrations 5.x)
- Upgrading to the builtin backend — CakePHP Book (Migrations 5.x)
- Database Seeding — CakePHP Book (Migrations 5.x)
- Running and Managing Migrations — CakePHP Book (Migrations 5.x)
- Release 5.0.0 — cakephp/migrations on GitHub, 2026-01-21
Written with substantial AI assistance. Facts checked against cited sources. Editorial policy.
Comments
No comments yet. Be the first to join the conversation.
Leave a comment
Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.
Related tools
- Directory profile
Docker
Container platform for building, sharing and running applications, including Docker Desktop, Docker Engine, Docker Hub and Docker Scout.
A tool connected to a service selected for this article. Compare its fit for your project; inclusion is not an endorsement.
- Directory profile
Git
Free, open-source distributed version control system for tracking code changes, branching and merging, created in 2005 and used by GitHub, GitLab and Bitbucket.
A tool connected to a service selected for this article. Compare its fit for your project; inclusion is not an endorsement.
More from Cloudsoft Zone

Industry updateCakePHP
What's new in CakePHP 5.4: ORM changes, DTO mapping and a built-in container
CakePHP 5.4 changes default eager loading, enforces entity-table ownership and adds request-to-DTO mapping, an optional built-in DI container, typed unhydrated queries and JSON streaming. Here is what to check before upgrading.
By Cloudsoft Zone

Resource guideWordPress
WordPress security hardening checklist for 2026 (WordPress 7.1)
A practical WordPress 7.1 hardening checklist based on the official handbook: updates, two-step logins, wp-config.php constants, file permissions, database privileges, firewalls, WP-CLI checksum checks and tested backups.
By Cloudsoft Zone

Troubleshooting guideCodeIgniter
CodeIgniter 4 validation not working? 9 causes and fixes
Fix CodeIgniter 4 validation problems fast: wrong input source, lost errors after redirect, permit_empty, strict JSON rules, is_unique placeholders and more.
By Cloudsoft Zone
