← Back to blog

How to Remove a Magento 2 Module Safely

Rodrigo Biazzi

Deleting a Magento module folder takes a second. Finding out a week later that product save fails in the admin with a "class does not exist" error takes much longer. Modules leave traces in the database that refer to their PHP classes, and when the classes disappear, those references break pages that seem unrelated to the module.

This article describes a removal process that avoids those surprises. It works for modules installed with Composer and for modules in app/code, on Magento Open Source and Adobe Commerce 2.4.

Why removal breaks things

A module rarely lives only in its own folder. Over time it writes to shared tables:

  • EAV attributes whose source_model, backend_model or frontend_model points to a class in the module. When the class is gone, loading or saving products, categories or customers with that attribute fails.
  • Widget instances whose type is a block class from the module.
  • CMS pages and blocks with widget directives that reference the module's blocks.
  • Orders paid with a payment method from the module. The order view in the admin needs the payment method class to render payment information.
  • Layout XML in your theme that references the module's blocks or templates.
  • Other modules that declare it in their sequence, require it in composer.json, or add plugins to its classes.

The process below finds each of these before the code goes away.

Step 1: find code dependencies

grep -rn "Vendor_Module" app/code app/design app/etc/config.php vendor/*/*/etc/module.xml
grep -rn "Vendor\\\\Module\\\\" app/code app/design --include=*.php --include=*.xml --include=*.phtml
composer why vendor/module-name

The first search finds module.xml sequences and layout references by module name. The second finds PHP class references, including plugins and preferences in di.xml. composer why shows which packages require it. Any hit has to be resolved first, either by removing that dependency or by keeping the module.

Step 2: find data dependencies

Look for references to the module's classes in the database. The % wildcard also matches the backslashes in class names, which avoids escaping problems:

SELECT attribute_id, entity_type_id, attribute_code, source_model, backend_model, frontend_model
FROM eav_attribute
WHERE source_model LIKE 'Vendor%Module%'
   OR backend_model LIKE 'Vendor%Module%'
   OR frontend_model LIKE 'Vendor%Module%';

SELECT instance_id, instance_type, title
FROM widget_instance
WHERE instance_type LIKE 'Vendor%Module%';

SELECT block_id, identifier FROM cms_block WHERE content LIKE '%Vendor%Module%';
SELECT page_id, identifier FROM cms_page WHERE content LIKE '%Vendor%Module%';

SELECT method, COUNT(*) FROM sales_order_payment GROUP BY method;

Also check catalog_eav_attribute.frontend_input_renderer for product attributes, and the module's own configuration under its section in core_config_data. The last query lists payment method codes on orders. If the module provides a payment method used on past orders, removing it will affect how those orders display. In that case, keep a minimal version of the payment method class, or accept the limitation knowingly.

Step 3: disable first, remove later

Disabling is reversible. Removing is not. Release the disable on its own and let it run in production for a while:

bin/magento module:disable Vendor_Module
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f
bin/magento cache:flush

This writes 'Vendor_Module' => 0 to app/etc/config.php, which you commit and deploy like any other change. A disabled module's classes are still present in the file system, so autoloading still works for EAV models. That means some data problems only appear after the code is gone. Test with the code physically removed in a local environment before the final release.

Step 4: clean up the data

Write the cleanup as a data patch in a project module, so it runs the same way on every environment. For an attribute you no longer need, remove it. For one you want to keep with its values, reset the model to a core class or to null:

<?php

declare(strict_types=1);

namespace Acme\Cleanup\Setup\Patch\Data;

use Magento\Catalog\Model\Product;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;

class RemoveVendorModuleAttributes implements DataPatchInterface
{
    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory
    ) {
    }

    public function apply(): self
    {
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);

        $eavSetup->removeAttribute(Product::ENTITY, 'vendor_badge');
        $eavSetup->updateAttribute(Product::ENTITY, 'vendor_color_group', 'source_model', null);

        return $this;
    }

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

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

The same patch can delete widget instances and update CMS content, or you can do those in the admin if there are only a few. More on writing patches in the declarative schema and data patches guide.

Step 5: remove the code

For a Composer package:

composer remove vendor/module-name

Magento also offers bin/magento module:uninstall for modules installed through Composer. It removes the package and, with --remove-data, runs the module's uninstall logic for database cleanup if the module provides it. Many third party modules do not, so do not count on it, and never run it with --remove-data without a fresh backup.

For a module in app/code, delete the folder with Git so the removal is part of the commit:

git rm -r app/code/Vendor/Module

Then remove the module's line from app/etc/config.php and its row from the module registry table:

DELETE FROM setup_module WHERE module = 'Vendor_Module';

The patch_list table keeps the class names of patches that ran. Leftover rows there do no harm, so cleaning them is optional.

Step 6: drop the tables

Magento does not drop the tables of a module whose code is gone. Its db_schema.xml and whitelist go with it, so nothing tells setup:upgrade that those tables exist. Find them by prefix, check that no other module uses them, take a backup, then drop them:

SELECT TABLE_NAME, TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME LIKE 'vendor\_module\_%';

If the module stored data you may need later, such as order level information, export it before dropping. Leaving unused tables in place is not harmful, so there is no rush with this step.

Step 7: rebuild and test

bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f
bin/magento indexer:reindex
bin/magento cache:flush

Test product save in the admin for products that had the module's attributes, the product grid, category pages, order view for old orders, CMS pages that used its widgets and checkout. Check var/log/exception.log for class not found errors.

Release plan across environments

Treat the removal as two releases, not one. The first release disables the module and ships the data patch that cleans attributes, widgets and content. It goes through local, staging and production in the normal way, and you watch the logs for a few days. The second release removes the code, the config.php entry and the setup_module row. Dropping tables can wait for a third, smaller change once everyone agrees the data is no longer needed.

Keep the backup taken before the second release for longer than usual. If someone discovers months later that a report depended on the module's table, you can restore that one table from the dump without touching the rest of the store.

Write down what you removed and why, in the repository or the project wiki. The next developer who finds an orphaned attribute or an old table will know where it came from.

If you are removing several modules after an audit, do them one or two at a time, so a problem points to a single module. The audit itself is described in the extension audit guide, and removing unused modules before a version upgrade makes the upgrade smaller.

FAQ

Is deleting the module folder enough to remove a Magento extension?

No. Attributes, widgets and CMS content can still reference its classes, and the store breaks when they load. Clean the data first and remove the module from config.php and setup_module.

Does module:uninstall work for modules in app/code?

It is meant for modules installed with Composer. For app/code modules, disable the module, clean the data, delete the folder and remove its entries from config.php and setup_module.

Does Magento drop the database tables of a removed module?

No. The tables stay in the database. Back up, confirm nothing else uses them and drop them yourself.

How do I fix a class does not exist error after removing a module?

Search eav_attribute, widget_instance and CMS content for the missing class name and update or remove those references. Then flush the cache.

I am available for freelance Magento and Adobe Commerce work, including extension cleanup. If you need help with this, get in touch.

Related articles