How to Remove a Magento 2 Module Safely
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_modelorfrontend_modelpoints 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
Hyvä Compatibility Modules: Make Luma Extensions Work
How Hyvä compatibility modules work: the CompatModuleRegistry, rewriting extension templates with Alpine.js, hyva_ layout handles and the Luma fallback.
Patching Magento 2 Core with Composer Patches
How to patch Magento 2 core and vendor code with cweagans/composer-patches and the Quality Patches Tool, create clean patch files and keep them upgrade safe.
Magento 2 Declarative Schema and Data Patches, Safely
Change the Magento 2 database without breaking production: db_schema.xml, the whitelist, data and schema patches, revertable patches and the dry-run mode.
Magento 2 Security Patches: How to Stay Up to Date
How Magento 2 security patches work: patch releases, isolated hotfixes, the Quality Patches Tool, how to apply them with Composer and how fast you should move.