Problem
When a package is uninstalled via Package Manager, there is no way for plugins or the package itself to execute code before file deletion begins. The only existing event, OnPackageUninstall, fires after the entire uninstall process is complete — at that point, all component files and DB objects are already removed.
This creates a real problem for packages that need to:
- Run database migration rollbacks (requires package classes to be loaded)
- Perform data cleanup using package services
- Gracefully disconnect from external services
- Log or export data before removal
Technical analysis
The uninstall call chain in the current code:
Uninstall.php::process() (line 81)
→ $this->package->uninstall($options)
→ modTransportPackage::uninstall() (line 389)
→ xPDOTransport::uninstall()
→ Iterates vehicles in REVERSE order (line 296)
→ xPDOFileVehicle::_uninstallFiles():
1. DELETE component files (line 129)
2. resolve() — runs PHP resolvers (line 156)
→ xPDOObjectVehicle::_uninstallObject():
1. REMOVE DB object (line 318)
2. resolve() — runs PHP resolvers (line 342)
→ OnPackageUninstall (line 92) ← fires here, everything already gone
The problem exists at multiple levels:
- MODX level:
OnPackageUninstall fires too late (after $this->package->uninstall() returns)
- xPDO level: within each vehicle, files are deleted before
resolve() is called; within resolve(), file-type resolvers run before php-type resolvers
The xPDO-level issues are deeper architectural problems. But the MODX-level fix is straightforward and immediately useful.
Proposed solution
Add an OnBeforePackageUninstall event that fires in Uninstall.php::process() before $this->package->uninstall() is called. At this point, all package files and database objects still exist.
// In Processors/Workspace/Packages/Uninstall.php::process()
$this->modx->invokeEvent('OnBeforePackageUninstall', [
'package' => $this->package,
]);
if ($this->package->uninstall($options) === false) {
// ...
}
$this->modx->invokeEvent('OnPackageUninstall', [
'package' => $this->package,
]);
This allows plugins to:
- Detect which package is being uninstalled via
$package->get('signature')
- Load and use the package's classes (autoloader still works, files exist)
- Run migration rollbacks, data exports, cleanup logic
- Optionally cancel the uninstall via
$modx->event->output(false) (consistent with OnBefore* patterns in other events)
Why this matters
Real-world example: MiniShop3 — an e-commerce package with complex data structures, migration system, and media files. When uninstalled via Package Manager:
- PHP resolvers fail because component files are already deleted
- No way to roll back database migrations
- No way to clean up related data (orders, products, media)
The same applies to any complex package with services, migrations, or cleanup logic.
Scope
This proposal only covers the MODX-level fix (adding the event). The deeper xPDO issues (file deletion order within vehicles/resolvers) are a separate concern that could be addressed in the xPDO repository.
Additional consideration
For symmetry, OnBeforePackageInstall and OnBeforePackageRemove could also be added, but OnBeforePackageUninstall is the most critical since it addresses a real data loss scenario.
Problem
When a package is uninstalled via Package Manager, there is no way for plugins or the package itself to execute code before file deletion begins. The only existing event,
OnPackageUninstall, fires after the entire uninstall process is complete — at that point, all component files and DB objects are already removed.This creates a real problem for packages that need to:
Technical analysis
The uninstall call chain in the current code:
The problem exists at multiple levels:
OnPackageUninstallfires too late (after$this->package->uninstall()returns)resolve()is called; withinresolve(), file-type resolvers run before php-type resolversThe xPDO-level issues are deeper architectural problems. But the MODX-level fix is straightforward and immediately useful.
Proposed solution
Add an
OnBeforePackageUninstallevent that fires inUninstall.php::process()before$this->package->uninstall()is called. At this point, all package files and database objects still exist.This allows plugins to:
$package->get('signature')$modx->event->output(false)(consistent withOnBefore*patterns in other events)Why this matters
Real-world example: MiniShop3 — an e-commerce package with complex data structures, migration system, and media files. When uninstalled via Package Manager:
The same applies to any complex package with services, migrations, or cleanup logic.
Scope
This proposal only covers the MODX-level fix (adding the event). The deeper xPDO issues (file deletion order within vehicles/resolvers) are a separate concern that could be addressed in the xPDO repository.
Additional consideration
For symmetry,
OnBeforePackageInstallandOnBeforePackageRemovecould also be added, butOnBeforePackageUninstallis the most critical since it addresses a real data loss scenario.