WordPress Plugin Uninstall Example: Clean Options and Tables Safely
Plan WordPress plugin uninstall cleanup safely with uninstall.php, option deletion, custom table cleanup, cron removal, and data retention rules.
Published
April 19, 2026
Reading Time
3 min read
Updated
April 19, 2026

Implementation Notes
Extension points, code paths, and implementation choices that should survive contact with production.
Best For
WordPress developers, agencies, and technical teams building custom plugin or theme functionality with cleaner operational defaults.
Primary Topics
Editorial Focus
Build Pattern: Extension points, code paths, and implementation choices that should survive contact with production. Updated on April 19, 2026.
Full Report
Last reviewed: April 19, 2026
Plugin uninstall behavior is easy to ignore until production sites accumulate stale options, custom tables, cron hooks, transients, and data nobody owns. The most common mistake is using deactivation as a destructive cleanup step. Deactivation and uninstall are not the same event.
This guide shows a WordPress plugin uninstall example using uninstall.php. It explains what to delete, what not to delete automatically, and how to make cleanup behavior safe for real sites.
Who this guide is for
This article is for WordPress plugin developers, agencies maintaining client plugins, and site operators who need predictable cleanup when a plugin is permanently removed.
Deactivation vs uninstall
Deactivation should stop runtime behavior: remove temporary scheduled jobs, flush temporary cache, or pause integrations. It should not delete permanent user data. Uninstall is the destructive path that runs when the plugin is deleted through WordPress.
If the plugin stores business records, logs, settings, or custom tables, define the uninstall policy clearly before launch. Some data should be deleted automatically. Some data should remain unless the administrator explicitly chooses a “delete all data” option.
Recommended structure
vulnwp-clean-plugin/
vulnwp-clean-plugin.php
uninstall.php
The uninstall.php file is loaded by WordPress during plugin deletion. It should be small, direct, and guarded by the WP_UNINSTALL_PLUGIN constant.
Example uninstall.php
<?php
/**
* Plugin uninstall cleanup.
*/
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
exit;
}
global $wpdb;
$option_names = array(
'vulnwp_plugin_settings',
'vulnwp_plugin_db_version',
);
foreach ( $option_names as $option_name ) {
delete_option( $option_name );
}
delete_transient( 'vulnwp_plugin_feed_cache_v1' );
$table_name = $wpdb->prefix . 'vulnwp_plugin_events';
$wpdb->query( \"DROP TABLE IF EXISTS {$table_name}\" );
This example deletes plugin-owned options, removes a transient, and drops a plugin-owned custom table. It does not try to delete posts, media, users, or arbitrary content because those may be user-created business data.
When to use register_uninstall_hook()
register_uninstall_hook() is useful for simple cleanup callbacks, but the callback must be a function or static method. Avoid registering it on every normal request. For many plugins, uninstall.php is clearer because WordPress can load it directly during deletion.
register_uninstall_hook( __FILE__, 'vulnwp_plugin_uninstall' );
function vulnwp_plugin_uninstall() {
delete_option( 'vulnwp_plugin_settings' );
}
Do not use anonymous functions for uninstall hooks. They cannot be serialized safely for this use case.
Multisite warning
Cleanup on multisite can be expensive. Looping through every site to delete options or tables may time out on large networks. For multisite plugins, decide whether data is site-specific, network-wide, or both, and test uninstall behavior on a staging copy of the network.
Cleanup checklist
- Delete plugin-owned options only when uninstall is intentional.
- Remove plugin-owned transients and temporary cache entries.
- Unschedule plugin-owned cron events during deactivation or uninstall.
- Drop custom tables only if the plugin policy says uninstall deletes data.
- Do not delete user content unless the administrator explicitly opted in.
- Document cleanup behavior in plugin docs and release notes.
Unschedule cron events
If the plugin registers scheduled events, remove them before or during uninstall. A stale cron event can keep trying to call code that no longer exists.
$timestamp = wp_next_scheduled( 'vulnwp_plugin_sync_event' );
while ( $timestamp ) {
wp_unschedule_event( $timestamp, 'vulnwp_plugin_sync_event' );
$timestamp = wp_next_scheduled( 'vulnwp_plugin_sync_event' );
}
For plugins with multiple scheduled hooks, keep the hook names in a documented list and clear them all. Cron cleanup should not depend on memory or tribal knowledge.
Safer destructive cleanup option
For plugins that store important records, add an explicit setting such as “Delete all plugin data on uninstall.” Store that choice as an option and read it inside uninstall.php.
$delete_data = (bool) get_option( 'vulnwp_delete_data_on_uninstall', false );
if ( ! $delete_data ) {
delete_option( 'vulnwp_delete_data_on_uninstall' );
return;
}
This avoids surprising administrators who only wanted to remove code temporarily or reinstall a plugin while preserving data.
How to test uninstall behavior
- Create a staging copy of the site.
- Activate the plugin and generate realistic options, cron events, and table rows.
- Deactivate the plugin and confirm permanent data still exists.
- Delete the plugin from wp-admin and confirm uninstall cleanup runs.
- Check the database for leftover plugin-owned options, tables, and cron events.
Do this before public release. Uninstall bugs are hard to fix after users have already deleted the plugin and lost data unexpectedly.
Common mistakes
- Deleting data on deactivation. Deactivation is reversible; uninstall is the destructive event.
- No direct access guard.
uninstall.phpmust checkWP_UNINSTALL_PLUGIN. - Dropping tables without policy. Custom tables may contain business records.
- Ignoring multisite scale. Network cleanup can be expensive and risky.
- Leaving cron hooks behind. Scheduled events can keep running references to removed code.
Related reading
This pairs with our dbDelta custom table example and the backup restore checklist. If a plugin creates data, it also needs a recovery and removal policy.


