clean up wp_options with an AI

My wp_options with the autostarts was overflowing. Had already grown to over 1 MB, which is bad for loading times. There are no tools that really clean up the options. For good reason.

wp_options

The wp_options table is a central component of every WordPress installation. It stores configurations, settings and temporary data – both from WordPress itself and from plugins and themes.

But this is precisely where the problem lies: over time, countless entries accumulate that are often no longer needed. It becomes particularly critical if these entries are marked as „autoload“. They are then loaded every time the page is called up – even if they have long been superfluous.

The result? Your website slows down, the database grows unnecessarily and you lose track of things. Many plugins leave their traces in wp_options, even if they have long since been deactivated or uninstalled.

So I wanted a plugin that would tidy up my wp_options. But that doesn’t exist. Nobody wants to put on that shoe, because it would be an insane amount of work to maintain the plugin.

wp_options

You would have to check which plugins are installed and what is in the wp_options. Then you would have to check which installed plugins appear in the options. Under different names, because don’t think that the developers would always use the same prefix. Nope, that would be easy.

Then there are also a lot of system autostarts in the wp_options that should not be deleted. Not to mention that themes also store data there. And then there are also SDKs for the Pro versions, which are used by many developers.

New plugins and new table entries come onto the market every day. Who is supposed to keep track of everything? Nobody can, not even an AI.

Keeping an overview

But an artificial intelligence comes close. That’s good enough for me. Whether it’s enough for you is up to you, use the plugin at your own risk, even if a backup is created.

So I fired up Gemini and told it what I wanted to do. This is called vibe coding. In the end, it’s more complicated and time-consuming than it sounds. Version 39 speaks volumes, doesn’t it?

How does the plugin work?

The plugin analyses your wp_options table and identifies potential junk data. It does this in several steps:

1. Recognising active plugins and themes

The plugin determines which plugins and themes are currently active. Only entries that belong to these active components are categorised as „secure“.

2. Heuristic analysis

Each entry in the wp_options is checked to see whether it belongs to an active plugin, theme or a known core prefix. If not, it is marked as a potential deletion candidate.

3. Output of the deletion candidates

A list appears with possible deletion candidates (green) and required entries (yellow).

Wp options reinigen

4. Output of an AI prompt

An extensive and (hopefully) very clear text output is generated that instructs an AI to create SQL commands that clean up the wp_options.

Wp options reinigen2

Check the list of deletion candidates and adjust the plugin’s output prompt if necessary. Remove entries that you are not sure can be deleted.

5. Transfer to an AI

Now select the AI of your least suspicion and insert the instruction text generated by the plugin as the prompt. The output should then be unique and generate three SQL commands for you:

  1. Create a backup of only the entries to be deleted
  2. Delete entries
  3. A restore command if something is wrong on the page afterwards.

Bildschirmfoto vom 2025 10 10 19 45 52

6. Execute commands

Now you have to open your phpMyAdmin and execute the first two commands there.

Bildschirmfoto vom 2025 10 10 19 40 40

Security

The results are displayed in a clear table. Active entries are highlighted in yellow, potential data waste in green. This allows you to retain control and decide for yourself what should be deleted.
AI-supported validation: The plugin generates a text block that you can send to an AI of your choice. This then creates a secure SQL deletion plan – including backup and restore option.

The plugin is designed so that it **does not delete any critical WordPress core options**. Instead, these entries are hidden from the outset to ensure maximum security. Nevertheless: **A backup of your database is mandatory** The plugin explicitly reminds you of this before you make any changes.

Responsibility

Check the list of deletion candidates and adjust the plugin’s output prompt if necessary. Remove entries that you are not sure can be deleted.

Target group and disclaimer

– **WordPress administrators** who want to clean up their database and optimise the loading times of their website.
– **Developers** who are looking for a tool to identify and remove unnecessary entries in the wp_options.
– **Technically savvy users** who are willing to manually check the results and take responsibility for the changes.

No plugin can guarantee 1100% that all identified entries are actually superfluous. Especially with plugins that do not use their prefixes consistently, misjudgements can occur. It is therefore important that you check the results carefully and, if in doubt, refrain from deleting them.

The **Autoload Options Cleaner V39** is a powerful tool for freeing your WordPress database from unnecessary clutter. It offers a clear overview, heuristic analysis and AI-supported validation – all to make your decision easier. But remember: **The responsibility is yours** Use the plugin wisely, perform backups and check the results carefully.

The Autoload Options Cleaner plugin

Save as autoload-cleaner/autoload-cleaner.php in your plugin directory and activate.

<?php
/**
 * Plugin Name: Autoload Options Cleaner V39
 * Description: Removes rigid plugin prefixes from heuristics and retains hard-coded exclusion for critical core options (e.g., user_roles). Use at your own risk.
 * Version: 39.0
 * Author: AI Assistant
 * License: GPL2
 */
// Security check: Prevent direct access
if (!defined('WPINC')) {
    die;
}
/**
 * Retrieves all active plugin prefixes (aggressive generic logic)
 * @return array
 */
function autoload_cleaner_get_active_prefixes() {
    $active_plugins = get_plugins();
    $prefixes = array();
    // 1. ADD core/standard prefixes (generic WP prefixes only)
    $prefixes[] = 'theme_mods_';
    $prefixes[] = 'widget_';
    $prefixes[] = '_transient_';
    $prefixes[] = '_site_transient_';
    $prefixes[] = 'cron';
    $prefixes[] = '_wp_session_';
    $prefixes[] = '_site_cron';

    // 2. Derive prefixes from ACTIVE plugin names (DYNAMIC)
    foreach ($active_plugins as $plugin_file => $plugin_data) {
        $folder_name = dirname($plugin_file);

        // A) Full folder name as prefix
        $clean_folder_name = str_replace(array('-', '/', ' '), '_', $folder_name);
        $prefixes[] = strtolower($clean_folder_name) . '_';

        // B) All main parts of the folder name as separate prefixes
        $parts = preg_split('/[_-]/', $folder_name, -1, PREG_SPLIT_NO_EMPTY);
        foreach ($parts as $part) {
            if (strlen($part) > 3) {
                $prefixes[] = strtolower($part) . '_';
            }
        }

        // C) Sanitized plugin title as prefix
        $clean_title = sanitize_title($plugin_data['Name']);
        $clean_title = str_replace('-', '_', $clean_title);
        if (substr($clean_title, -1) !== '_') {
            $prefixes[] = strtolower($clean_title) . '_';
        } else {
            $prefixes[] = strtolower($clean_title);
        }
    }

    $prefixes = array_unique($prefixes);

    // Remove generic noise
    $prefixes = array_diff($prefixes, ['wp_', 'pro_', 'the_', 'for_', 'and_', 'settings_']);

    // Sort by length (longest first) for more accurate matches
    usort($prefixes, function($a, $b) {
        return strlen($b) - strlen($a);
    });

    return $prefixes;
}

/**
 * Attempts to associate an option with an active plugin or theme (pure heuristic)
 * @param string $option_name
 * @return array|false
 */
function autoload_cleaner_identify_plugin($option_name) {
    global $wpdb;
    static $prefixes_map = null;

    $db_prefix = $wpdb->prefix;

    // Hard-code protection for user roles (retain as fallback for identification)
    if (strpos($option_name, 'user_roles') !== false && strlen($option_name) < (strlen($db_prefix) + 15)) {
        return ['status' => 'active', 'name' => 'CORE: CRITICAL USER ROLES OPTION (SAFE)', 'prefix' => 'CORE'];
    }

    if ($prefixes_map === null) {
        $active_plugins = get_plugins();
        $prefixes_map = [];

        // 1. ADD theme-specific prefixes (must protect)
        $stylesheet_name = get_stylesheet();
        $template_name = get_template();
        // Theme mods (Customizer/Widget) prefixes
        $prefixes_map['theme_mods_' . $stylesheet_name] = 'Core/Theme Settings (Active)';
        $prefixes_map['theme_mods_' . $template_name] = 'Core/Theme Settings (Active)';

        // Direct theme options table (often theme name as prefix)
        $clean_stylesheet = str_replace(array('-', '/', ' '), '_', $stylesheet_name);
        $prefixes_map[strtolower($clean_stylesheet) . '_'] = 'Active Theme Options';

        $clean_template = str_replace(array('-', '/', ' '), '_', $template_name);
        $prefixes_map[strtolower($clean_template) . '_'] = 'Active Theme (Parent) Options';

        // 2. Derive plugin prefixes from active plugin names
        foreach ($active_plugins as $plugin_file => $plugin_data) {
            $folder_name = dirname($plugin_file);
            $plugin_name = $plugin_data['Name'];
            $plugin_prefixes = [];

            $clean_folder_name = str_replace(array('-', '/', ' '), '_', $folder_name);
            $plugin_prefixes[] = strtolower($clean_folder_name) . '_';

            $parts = preg_split('/[_-]/', $folder_name, -1, PREG_SPLIT_NO_EMPTY);
            foreach ($parts as $part) {
                if (strlen($part) > 3) {
                    $plugin_prefixes[] = strtolower($part) . '_';
                }
            }

            $clean_title = sanitize_title($plugin_data['Name']);
            $clean_title = str_replace('-', '_', $clean_title);
            if (substr($clean_title, -1) !== '_') {
                $plugin_prefixes[] = strtolower($clean_title) . '_';
            } else {
                $plugin_prefixes[] = strtolower($clean_title);
            }

            foreach (array_unique($plugin_prefixes) as $prefix) {
                $prefixes_map[$prefix] = $plugin_name;
            }
        }
    }

    // 3. Check core/standard prefixes first (widget_, _transient_, theme_mods_, etc.)
    $core_prefixes = [
        'theme_mods_' => 'Core/Theme Settings',
        'widget_' => 'Core/Widget Settings',
        '_transient_' => 'Core/Temporary Cache',
        '_site_transient_' => 'Core/Temporary Cache',
        'cron' => 'Core/Scheduled Tasks',
        '_wp_session_' => 'Core/Session Data',
        'wp_ulike_' => 'WP ULike (Generic Prefix)', // Can remain
        'wpp_' => 'WordPress Popular Posts (WPP) (Generic Prefix)', // Can remain
        'fs_' => 'Freemius SDK (Generic)' // Can remain, as SDK is often universal
    ];

    foreach ($core_prefixes as $prefix => $name) {
        if (strpos($option_name, $prefix) === 0) {
            return ['status' => 'active', 'name' => $name, 'prefix' => $prefix];
        }
    }

    // 4. Check options against the created mapping table
    foreach ($prefixes_map as $prefix => $plugin_name) {
        if (strpos($option_name, $prefix) === 0) {
            return ['status' => 'active', 'name' => $plugin_name, 'prefix' => $prefix];
        }
    }

    // If nothing is found, it's garbage data
    return ['status' => 'inactive', 'name' => 'DEACTIVATED PLUGIN/THEME (Garbage Data)'];
}

/**
 * Adds the plugin menu.
 */
function autoload_cleaner_menu() {
    add_management_page(
        'Autoload Cleaner',
        'Autoload Cleaner',
        'manage_options',
        'autoload-cleaner',
        'autoload_cleaner_page_content'
    );
}
add_action('admin_menu', 'autoload_cleaner_menu');

/**
 * Generates the admin page content.
 */
function autoload_cleaner_page_content() {
    global $wpdb;
    $db_prefix = $wpdb->prefix;
    $options_table_name = $db_prefix . 'options';

    // *** CRITICAL: List of core options that should NOT be displayed ***
    $core_options_to_hide = [
        'user_roles',
        'siteurl',
        'home',
        'blogname',
        'blogdescription',
        'admin_email',
        'start_of_week',
        'timezone_string',
        'gmt_offset',
        'default_role',
        'WPLANG',
        'upload_path',
        'uploads_use_yearmonth_folders',
        'mailserver_url',
        'mailserver_login',
        'mailserver_pass',
        'mailserver_port',
        'mailserver_auth',
        'permalink_structure',
        'category_base',
        'tag_base'
    ];
    // ************************************************************************
    $limit = 500;
    $min_size = 50;
    $sql = $wpdb->prepare(
        "SELECT option_id, option_name, LENGTH(option_value) as option_size
         FROM {$options_table_name}
         WHERE autoload = 'yes' AND LENGTH(option_value) >= %d
         ORDER BY option_size DESC
         LIMIT %d",
        $min_size, $limit
    );
    $results = $wpdb->get_results($sql);
    $total_autoload_size = $wpdb->get_var("SELECT SUM(LENGTH(option_value)) FROM {$options_table_name} WHERE autoload = 'yes'");
    $active_plugins_list = [];
    $active_plugins_bullet_list = [];
    $active_plugins = get_plugins();
    foreach ($active_plugins as $plugin_file => $plugin_data) {
        $active_plugins_list[] = $plugin_data['Name'];
        $active_plugins_bullet_list[] = '* ' . $plugin_data['Name'];
    }

    $active_theme_name = wp_get_theme()->get('Name');
    $active_plugins_bullet_list[] = '* ACTIVE THEME: ' . $active_theme_name;

    $delete_candidates_list = [];
    echo '<div class="wrap"><h1>Autoload Options Cleaner V39</h1>';
    echo '<p>Your database prefix is: <strong>' . esc_html($db_prefix) . '</strong></p>';
    echo '<p>Total size of all autoloaded options: <strong>' . size_format($total_autoload_size, 2) . '</strong></p>';
    echo '<p><strong>WARNING: ALWAYS create a current database backup before deleting anything! Use at your own risk!</strong></p>';
    if (!$results) {
        echo '<p>No autoloaded options (over 50 bytes) found.</p></div>';
        return;
    }
    // Table for visual inspection
    echo '<h2>1. Visual inspection of the ' . count($results) . ' largest autoload entries (min. 50 bytes)</h2>';
    echo '<p>Color coding (green = potential garbage) is heuristic only. **WordPress core options are hidden to ensure maximum safety.**</p>';
    echo '<table class="wp-list-table widefat fixed striped">';
    echo '<thead><tr><th>Option Name</th><th>Size</th><th>Assignment</th></tr></thead>';
    echo '<tbody>';
    foreach ($results as $result) {
        // --- HARD EXCLUSION FOR CORE OPTIONS (user_roles etc.) ---
        // Remove the prefix for comparison
        $option_core_name = str_replace($db_prefix, '', $result->option_name);
        if (in_array($option_core_name, $core_options_to_hide, true)) {
            continue; // Skip and do not display this option
        }
        // --------------------------------------------------------
        $identification = autoload_cleaner_identify_plugin($result->option_name);
        $status_data = $identification;

        $class = '';
        $hint = '';

        if ($status_data['status'] === 'inactive') {
            // GREEN: Deletion candidate
            $class = 'style="background-color: #d4edda; color: #155724;"';
            $hint = 'SAFE: Probably belongs to a ' . esc_html($status_data['name']) . '.';
            $delete_candidates_list[] = [
                'name' => $result->option_name,
                'size' => size_format($result->option_size, 2),
                'option_id' => $result->option_id
            ];
        } else {
            // YELLOW: Active or core plugin/theme
            $class = 'style="background-color: #fff3cd; color: #856404;"';
            $hint = 'Active: ' . esc_html($status_data['name']);
        }
        echo '<tr ' . $class . '>';
        echo '<td><strong>' . esc_html($result->option_name) . '</strong></td>';
        echo '<td>' . size_format($result->option_size, 2) . '</td>';
        echo '<td>' . esc_html($hint) . '</td>';
        echo '</tr>';
    }
    echo '</tbody></table>';

    // --- AI/MANUAL ANALYSIS BLOCK ---
    echo '<div style="margin-top: 30px; border: 1px solid #c3c4c7; padding: 20px; background-color: #f7f7f7;">';
    echo '<h2>2. AI Analysis Basis (For Copy & Paste Use)</h2>';
    echo '<p>Copy the following text block completely and paste it into a message to the AI. The AI **must** create a safe SQL deletion plan.</p>';
    // --- The pure text block for AI communication starts here ---
    $analysis_text = "Please compare the list of autoload entries (deletion candidates) with the installed plugins and the active theme, and create a final, safe deletion plan (SQL commands).
**CRITICAL INSTRUCTIONS FOR THE AI (HALLUCINATION PROHIBITION & PREFIX CORRECTION):**
1. **MANDATORY DATABASE PREFIX:** The prefix '{$db_prefix}' MUST be used in all SQL commands instead of the standard prefix 'wp_'. The table is named `{$db_prefix}options`.
2. **MANDATORY DELETION LOGIC:** Only the entries in list 2 are active. All options with prefixes from **inactive** plugins or **inactive** themes **MUST** be considered remnants (garbage) and proposed for deletion. Options belonging to the ACTIVE THEME listed in list 2 MUST NOT be deleted.
3. **CORE OPTIONS EXCLUDED:** The displayed entries do not contain any known WordPress core options (such as *user_roles*).
**SQL FORMATTING (OUTPUT MUST BE SEPARATE):**
The resulting deletion plan MUST contain THREE SQL commands, each in a SEPARATE SQL CODE BLOCK:
A. **BACKUP:** A command that creates a timestamped backup table (e.g., {$db_prefix}options_backup_autoload_YYYYMMDD_HHMMSS) of the options to be deleted.
B. **DELETE:** The DELETE command.
C. **RECOVERY:** The complete SQL command to restore the backed-up entries from the backup table. **CRITICAL:** You MUST use **INSERT IGNORE INTO** (not just INSERT INTO) to prevent error **#1062 (Duplicate entry)**. The SQL line MUST start with **INSERT IGNORE INTO**.
### 1. DATABASE PREFIX (TO BE USED FOR ALL TABLE NAMES)
{$db_prefix}
### 2. CURRENTLY INSTALLED PLUGINS & ACTIVE THEME (ONLY THESE ARE ACTIVE)
" . implode("\n", $active_plugins_bullet_list) . "
### 3. AUTOLOAD OPTIONS (CONFIRMED RESIDUES FOR DELETION)
";
    if (!empty($delete_candidates_list)) {
        foreach ($delete_candidates_list as $item) {
            $analysis_text .= $item['name'] . ' (' . $item['size'] . ', ID: ' . $item['option_id'] . ')' . "\n";
        }
    } else {
        $analysis_text .= "No deletion candidates found.";
    }
    // Now display the actual textarea with the collected data
    $row_count = count($delete_candidates_list) + count($active_plugins_list) + 28;
    echo '<textarea rows="' . $row_count . '" cols="100" onclick="this.select();" readonly style="width: 100%; min-height: 400px; font-family: monospace; font-size: 14px; background-color: #fff; border: 1px solid #ccc;">' . esc_textarea($analysis_text) . '</textarea>';

    echo '</div>'; // End AI Block

    echo '</div>'; // End wrap
}
?>

 

Über den Autor

Hessi

Hessi

Michael "Hessi" Hessburg ist ein erfahrener Technik-Enthusiast und ehemaliger Informatiker. Seine Website, die er seit über 25 Jahren betreibt, deckt vielfältige Themen ab, darunter Haus & Garten, Hausrenovierung, IT, 3D-Druck, Retrocomputing und Autoreparatur. Zudem behandelt er gesellschaftspolitische Themen wie Datenschutz und Überwachung. Hessi ist seit 20 Jahren freiberuflicher Autor und bietet in seinem Blog fundierte Einblicke und praktische Tipps. Seine Beiträge sind sorgfältig recherchiert und leicht verständlich, um Leser bei ihren Projekten zu unterstützen.

Schreibe einen Kommentar

Ich bin mit der Datenschutzerklärung und der Speicherung meiner eingegebenen Daten einverstanden.