Magento 2 Data Patches and Setup Scripts

Magento 2 Data Patches and Setup Scripts

November 15, 2025 · By Magento Company
Magento 2 Data Patches and Setup Scripts

Structure belongs in declarative schema; data changes belong in patches. Adding an attribute, seeding config values, migrating rows - anything that writes data at install or upgrade time is a patch. The patch system replaced the old UpgradeData.php version-maze with explicit, dependency-ordered classes. Here is how to write them well.

The Anatomy

namespace Acme\Locations\Setup\Patch\Data;

use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;

class AddDefaultLocation implements DataPatchInterface, PatchRevertableInterface
{
    public function __construct(
        private ModuleDataSetupInterface $setup
    ) {}

    public function apply(): void
    {
        $this->setup->startSetup();
        // insert default location row
        $this->setup->endSetup();
    }

    public function revert(): void
    {
        // remove the row again (module uninstall)
    }

    public static function getDependencies(): array
    {
        return [CreateLocationTableConfig::class];
    }

    public function getAliases(): array
    {
        return [];
    }
}

Key mechanics:

  • Patches run once - recorded in the patch_list table
  • getDependencies() declares order: your patch runs after its dependencies. This replaces version-number ordering entirely
  • apply() wraps writes in startSetup/endSetup (transaction and foreign-key handling)
  • PatchRevertableInterface::revert() runs on module uninstall via setup:uninstall - implement it when the patch’s data should not outlive the module

The Discipline

Idempotency: patches should be safe to reason about as run-once, but defensive checks (“does this attribute already exist?”) save you when a database was built by import rather than setup:upgrade.

One concern per patch: a patch adding an attribute and a patch seeding its default values are two patches with a dependency - not one patch doing both. Small patches are auditable and orderable; big patches are neither.

No schema in data patches: table and column creation is db_schema.xml’s job. A data patch may reference schema from its dependencies, never create it (schema patches exist for legacy reasons; declarative schema is the modern path).

Legacy Scripts and Migration

Old modules with InstallData/UpgradeData still run - Magento executes them before patches. When modernising: leave old scripts in place for existing installs (they are recorded as run), and write all new changes as patches. The ModuleDataSetupInterface API is the same in both, so the migration is about structure, not relearning.

Operational Notes

  • bin/magento setup:upgrade runs pending patches; bin/magento module:status and the patch_list table tell you what has applied
  • A failed patch stops the run and can leave setup:upgrade half-applied - test patches against a production-like database, especially data-heavy ones (backfills over millions of rows should be batched within the patch, not one heroic UPDATE)

Patches are small, ordered, honest units of data change. Write them that way and upgrades stop being the part of the release everyone holds their breath through.

Database Development Magento 2