# Remove Leftover Data From Uninstalled Plugins

> Adds Tools > Leftover Plugin Data, where you enter a prefix (such as old_slider_) to see how many options, post meta, user meta and scheduled tasks use it, and delete them after reviewing the counts.

- Type: PHP
- Runs: Admin only
- Category: [Performance](https://wpide.com/library/category/performance/)
- Tags: database, cleanup, plugins
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/perf-leftover-plugin-data-cleanup/

## Code

```php
add_action( 'admin_menu', function () {
    add_management_page( 'Leftover Plugin Data', 'Leftover Plugin Data', 'manage_options', 'wpide-leftovers', function () {
        global $wpdb;

        $prefix  = isset( $_POST['wpide_prefix'] ) ? sanitize_key( wp_unslash( $_POST['wpide_prefix'] ) ) : '';
        $blocked = array( 'wp_', 'wp', '_wp', 'widget', 'theme', 'user', 'site', 'blog', 'woocommerce', 'wc_', '_transient', '_site_transient' );
        $valid   = strlen( $prefix ) >= 4 && ! in_array( $prefix, $blocked, true ) && 0 !== strpos( $prefix, $wpdb->prefix );
        $counts  = array();

        if ( $prefix && isset( $_POST['_wpnonce'] ) && check_admin_referer( 'wpide_leftovers' ) ) {
            if ( ! $valid ) {
                echo '<div class="notice notice-error"><p>Please use a more specific prefix (at least 4 characters, not a WordPress prefix).</p></div>';
            } else {
                $like   = $wpdb->esc_like( $prefix ) . '%';
                $counts = array(
                    'Options'   => (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE %s", $like ) ),
                    'Post meta' => (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key LIKE %s", $like ) ),
                    'User meta' => (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key LIKE %s", $like ) ),
                );
                $cron = 0;
                foreach ( (array) _get_cron_array() as $hooks ) {
                    foreach ( array_keys( (array) $hooks ) as $hook ) {
                        $cron += 0 === strpos( $hook, $prefix ) ? 1 : 0;
                    }
                }
                $counts['Scheduled tasks'] = $cron;

                if ( isset( $_POST['wpide_delete_leftovers'] ) && current_user_can( 'manage_options' ) ) {
                    $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $like ) );
                    $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE %s", $like ) );
                    $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s", $like ) );
                    foreach ( (array) _get_cron_array() as $hooks ) {
                        foreach ( array_keys( (array) $hooks ) as $hook ) {
                            if ( 0 === strpos( $hook, $prefix ) ) {
                                wp_unschedule_hook( $hook );
                            }
                        }
                    }
                    wp_cache_flush();
                    printf( '<div class="notice notice-success"><p>%s</p></div>', esc_html( sprintf( 'Deleted %d item(s) starting with "%s".', array_sum( $counts ), $prefix ) ) );
                    $counts = array();
                }
            }
        }

        echo '<div class="wrap"><h1>Leftover Plugin Data</h1><form method="post">';
        wp_nonce_field( 'wpide_leftovers' );
        echo '<p><label for="wpide-prefix">Prefix used by the removed plugin</label><br><input type="text" id="wpide-prefix" name="wpide_prefix" value="' . esc_attr( $prefix ) . '" class="regular-text" placeholder="old_slider_"></p>';
        submit_button( 'Preview', 'secondary', 'wpide_preview', false );
        if ( $counts && array_sum( $counts ) ) {
            echo '<table class="widefat striped" style="max-width:420px;margin:1em 0"><tbody>';
            foreach ( $counts as $label => $count ) {
                printf( '<tr><td>%s</td><td>%s</td></tr>', esc_html( $label ), esc_html( number_format_i18n( $count ) ) );
            }
            echo '</tbody></table>';
            submit_button( 'Delete these items', 'delete', 'wpide_delete_leftovers', false, array( 'onclick' => "return confirm('Delete all items with this prefix? This cannot be undone.');" ) );
        } elseif ( $counts ) {
            echo '<p>Nothing uses this prefix.</p>';
        }
        echo '</form></div>';
    } );
} );
```

## Notes

Prefixes shorter than 4 characters and WordPress's own prefixes are refused; always preview first and keep a backup.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Remove Leftover Data From Uninstalled Plugins" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
