Back to Blog
Lesson 37 of the Advanced WordPress Plugin Engineering: Scale, Security & React UIs course
WordPressJune 28, 20263 min read

Plugin Lifecycle Management: Migrations and Secure Uninstallation

Master WordPress Plugin Lifecycle management. Learn to handle version-specific database migrations and implement professional, non-destructive uninstall routines.

WordPressLifecycleDatabase ManagementUninstallPHPplugin-development

Previously in this course, we covered Versioning and Release Management: Professional WordPress Plugin Standards. While versioning handles the "what" and "when" of your updates, this lesson focuses on the "how"—specifically, managing the state of your plugin’s data as it evolves over time.

Professional plugin engineering requires more than just adding features; it demands a defensive approach to how your code interacts with the WordPress database during installation, updates, and removal.

The Plugin Lifecycle Architecture

A robust plugin lifecycle is governed by three primary events: activation, updates, and uninstallation. In a production-grade plugin, these should never be handled by scattered logic in your main plugin file. Instead, we treat the lifecycle as a series of state transitions.

1. Version-Specific Database Migrations

As discussed in Integration Testing for WordPress: Database and API Workflows, database changes are the most common source of production outages. You should never rely on simple dbDelta calls without tracking the current version of your schema.

We implement a MigrationManager that checks the version stored in wp_options against the current plugin version constant.

PHP
namespace MyPlugin\Lifecycle;

class MigrationManager {
    public function maybe_migrate() {
        $installed_version = get_option('my_plugin_version', '0.0.0');
        $current_version = MY_PLUGIN_VERSION;

        if (version_compare($installed_version, $current_version, '<')) {
            $this->run_migrations($installed_version, $current_version);
            update_option('my_plugin_version', $current_version);
        }
    }

    private function run_migrations($from, $to) {
        #6A9955">// Logic to execute specific SQL files based on version gaps
        if (version_compare($from, '1.1.0', '<')) {
            $this->migrate_to_1_1_0();
        }
    }
}

2. The Uninstall Strategy

The uninstall.php file is a special file that WordPress executes only when a user deletes a plugin from the admin dashboard. Note that this file runs in a separate process, meaning it does not have access to your defined classes or constants.

Crucial: Always check defined('WP_UNINSTALL_PLUGIN') to prevent direct access to the file.

PHP
#6A9955">// uninstall.php
if (!defined('WP_UNINSTALL_PLUGIN')) {
    exit;
}

global $wpdb;

#6A9955">// Only delete data if the user hasn't opted to "Keep Data"
$keep_data = get_option('my_plugin_keep_data_on_uninstall', false);

if (!$keep_data) {
    $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}my_plugin_data");
    delete_option('my_plugin_settings');
}

Hands-on Exercise: Implementing a Migration Hook

Your task is to integrate a migration check into the plugin's boot process.

  1. Create a LifecycleServiceProvider that hooks into admin_init.
  2. Within the service, create a method that compares the stored database version against your MY_PLUGIN_VERSION constant.
  3. If an update is required, trigger a log event (to verify it ran) and update the my_plugin_version option.
  4. Challenge: Ensure your migration logic uses $wpdb->prepare as defined in Preventing SQL Injection in WordPress: A Deep Dive into $wpdb.

Common Pitfalls

  • Assuming Classes Exist in uninstall.php: As noted, uninstall.php is isolated. Keep your logic procedural here. If you need to share logic, use a separate file that you require carefully, ensuring no dependencies on the main plugin bootstrapper.
  • Destructive Uninstalls: Never delete user data by default. Always provide an option in your settings panel to "Delete data on uninstall." If the user deletes the plugin, they might intend to reinstall it later; wiping their database without consent is a major UX failure.
  • Race Conditions: If your plugin is network-activated on a Multisite, lifecycle hooks behave differently. Always use is_network_admin() checks if you are modifying global tables.

Recap

Proper lifecycle management ensures your plugin is a "good citizen" in the WordPress ecosystem. By versioning your database schema, using uninstall.php defensively, and providing users control over their data, you build trust and maintain stability. Always treat your database as the long-lived component of your software that must survive even when the plugin code itself is removed.

Up next: Performance Monitoring — adding instrumentation to track query times and system health.

Similar Posts