Skip to content

Custom uninstall UI: allow packages to define uninstall options and messages #16917

@biz87

Description

@biz87

Problem

The Package Manager uninstall dialog currently shows a fixed set of three radio buttons (preexisting_mode: Preserve / Uninstall / Restore). There is no way for a package to:

  1. Show a custom message before uninstall (e.g., "This package contains 1,523 orders and 450 products")
  2. Present custom options (e.g., checkboxes: "Remove database tables", "Delete media files", "Export data before removal")
  3. Collect user input (e.g., a "Reason for removal" textarea)
  4. Act on these choices during the uninstall process (in resolvers or events)

Complex packages like e-commerce systems, CRMs, or migration-heavy extras have no way to offer a controlled, user-guided uninstall experience. The user either gets a full uninstall with no options, or the package has to build an entirely separate uninstall page outside the standard workflow.

Current uninstall flow

User clicks "Uninstall" on package grid
    → MODx.window.PackageUninstall dialog
        (3 radio buttons for preexisting_mode, nothing else)
    → User clicks "Uninstall"
    → AJAX: Workspace/Packages/Uninstall {signature, preexisting_mode}
    → Processor: $package->uninstall($options)
    → OnPackageUninstall event (too late, everything deleted)

No step in this chain allows the package to inject custom UI or custom parameters.

Proposed solution

1. Package declares uninstall setup

During package build, the developer registers an uninstall setup configuration — either as a static definition in transport attributes, or as a PHP file that returns a dynamic configuration.

Static approach (transport attributes during build):

$package->setAttribute('uninstall-setup', [
    'message' => 'MiniShop3 contains important e-commerce data.',
    'fields' => [
        [
            'xtype' => 'xcheckbox',
            'name' => 'remove_tables',
            'boxLabel' => 'Remove database tables (orders, products, categories)',
        ],
        [
            'xtype' => 'xcheckbox',
            'name' => 'remove_media',
            'boxLabel' => 'Remove uploaded product images',
        ],
    ],
]);

Dynamic approach (PHP file):

A file at a conventional path (e.g., {core_path}components/{package}/setup/uninstall-setup.php) returns the configuration at runtime. This allows dynamic content — counting records, checking disk usage, etc.:

<?php
// uninstall-setup.php — executed when the uninstall dialog opens
$orderCount = $modx->getCount('MiniShop3\Model\msOrder');
$productCount = $modx->getCount('MiniShop3\Model\msProductData');

return [
    'message' => "MiniShop3 contains {$orderCount} orders and {$productCount} products.",
    'fields' => [
        [
            'xtype' => 'xcheckbox',
            'name' => 'remove_tables',
            'boxLabel' => "Drop all database tables ({$orderCount} orders will be lost)",
        ],
        [
            'xtype' => 'xcheckbox',
            'name' => 'remove_media',
            'boxLabel' => 'Delete uploaded media files from filesystem',
        ],
        [
            'xtype' => 'textarea',
            'name' => 'removal_reason',
            'fieldLabel' => 'Reason for removal (optional)',
            'anchor' => '100%',
        ],
    ],
];

2. Manager UI loads the setup

Before showing the uninstall dialog, the manager makes a request to a new processor (e.g., Workspace/Packages/GetUninstallSetup) which:

  1. Checks if the package has an uninstall-setup attribute or a setup file
  2. Executes the setup file if present (or reads static attributes)
  3. Returns the form configuration to the client

The MODx.window.PackageUninstall dialog then renders the custom fields above the standard preexisting_mode radios. If no custom setup is defined, the dialog works exactly as before.

3. Custom options flow to server

When the user submits the dialog, all form values (standard + custom) are sent to the Uninstall processor. The processor includes them in the $options array:

// In Uninstall.php::process()
$options = [
    xPDOTransport::PREEXISTING_MODE => $this->getProperty('preexisting_mode'),
    // Custom options from the package's uninstall setup form:
    'remove_tables' => $this->getProperty('remove_tables'),
    'remove_media' => $this->getProperty('remove_media'),
    'removal_reason' => $this->getProperty('removal_reason'),
];

4. Resolvers and events receive custom options

PHP resolvers during uninstall receive the full $options array and can act on the user's choices:

// In a package's PHP resolver:
if ($options[xPDOTransport::PACKAGE_ACTION] === xPDOTransport::ACTION_UNINSTALL) {
    if (!empty($options['remove_tables'])) {
        // Drop package tables
        $modx->exec('DROP TABLE IF EXISTS ...');
    }
    if (!empty($options['remove_media'])) {
        // Remove uploaded files
    }
    if (!empty($options['removal_reason'])) {
        // Log or send analytics
    }
}

The OnBeforePackageUninstall event (proposed in #16916) would also receive these options, enabling plugins to react to them.

Use cases

Use case Custom field Resolver action
E-commerce: preserve orders Checkbox "Remove tables" Conditionally drop tables
Media-heavy package Checkbox "Delete uploaded files" Conditionally clean filesystem
SaaS integration Message "Disconnect from service first!" Informational, no action
Analytics / feedback Textarea "Reason for removal" Send to analytics endpoint
Migration system Checkbox "Rollback migrations" Run migration rollback

Backwards compatibility

  • Packages without uninstall-setup — the dialog works exactly as before
  • The standard preexisting_mode radio buttons remain unchanged
  • Existing resolvers continue to work — custom options are additive
  • No changes to the xPDO transport system required

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions