ENGINEERING BLOG

How Plugins and Themes Should Manage Autoload in wp_options (And Why It Matters)

Written by: Saurab Gupta
19 mins read
Diagram showing WordPress wp_options table with the autoload column highlighted

Every time someone visits a WordPress site — or an admin opens the dashboard — WordPress loads thousands of settings from the database. Not because they’re needed on that page, but because the system is designed to load a specific set of options preemptively. This is the autoload column at work, and it’s one of the most misunderstood levers in WordPress autoload wp_options performance tuning.

A typical plugin-heavy WordPress site accumulates hundreds — sometimes thousands — of autoloaded options that sit unused on every page load. Each one consumes memory and adds to the deserialization cost. Collectively, they can add several megabytes of overhead per request. The good news: WordPress 6.6 introduced real tools to fix this, and with the right practices, plugin and theme developers don’t have to contribute to the bloat.

Quick aside: this isn’t the same as PHP’s class autoloading, which is about how class files get located and loaded — not how database options get cached. If you’re tuning that side of the stack too, High-Performance PHP Autoloading with O(1) Classmaps is worth a look.

What Autoload Does in wp_options

When WordPress boots on any request, it runs a query that pulls every option flagged for autoloading and caches the results in a global array called $alloptions. From that point forward, any get_option() call for one of those options is served from the cache — no extra database query needed.

The intent is sound: one combined query beats hundreds of small ones. The problem is that this payload loads whether it’s used on the current request or not.

A Typical Autoload Breakdown

  • Base WordPress options: roughly 100 entries (site name, timezone, home URL, permalink structure, etc.)
  • 15 active plugins averaging 3 options each: roughly 45 entries
  • Theme options: roughly 50 entries
  • Leftover data from removed plugins: roughly 500 entries
  • Typical total: around 700 autoloaded options

Multiply that by the size of each serialized option — anywhere from 100 bytes to tens of kilobytes — and a site can easily end up with several megabytes of data loaded on every single request, before a single line of page-specific code runs.

Why Site Owners Feel This More Than Frontend Visitors

Frontend pages are often shielded by page caching, a CDN, or service workers, which hides the autoload overhead. But several areas of a site take the full hit on every request:

  • The WordPress admin dashboard, which is uncached and rebuilds the autoload payload on every click
  • WooCommerce checkout and other dynamic, uncached pages
  • Database backups, which grow larger than necessary
  • Cache warm-up times, since there’s simply more data to load and cache

A common pattern: the site owner sees a fast, cached frontend but a sluggish admin area. Autoload bloat is frequently the underlying cause.

The Mechanism: How wp_options Actually Works

The Options API is WordPress’s key-value store. Every plugin, theme, and core feature uses it to persist settings between requests.

The Table Structure

option_idoption_nameoption_valueautoload
1siteurlhttps://example.comon
2homehttps://example.comon
3blognameMy Blogon
1500my_plugin_color#0073aaon
1501my_plugin_api_keysk_live_abc123…off
1502my_plugin_cache[serialized array]off

The autoload column determines whether an option is pulled into memory on every request. It is the single most important factor in whether an option contributes to autoload bloat.

Correction: The original draft described the column as strictly binary (yes / no). As of WordPress 6.6+, the column can also hold on, off, auto, auto-on, and auto-off — the legacy yes/no strings still work but are considered deprecated as of WordPress 6.7. Any option whose value is on, auto-on, auto, or the legacy yes is included in the autoload payload.

How get_option() Works

The function actually responsible for filling that cache is wp_load_alloptions(). It’s called the first time get_option() runs on a request — not once per call. Internally it does roughly this:

  1. Check whether $alloptions already exists for this request (in-memory) or in the persistent object cache under the key alloptions.
  2. If not present, run a single query — SELECT option_name, option_value FROM wp_options WHERE autoload IN (...) — covering every “on” variant (on, auto-on, auto, and the legacy yes).
  3. Store the entire result set as $alloptions, both for the rest of this request and in the persistent cache for future requests.

Every subsequent get_option('blogname') call on that request simply reads from this already-populated $alloptions array — no database hit. If the option isn’t in $alloptions (i.e., it’s not autoloaded), get_option() falls back to a separate, individual query for that one option, which is then cached on its own for the rest of the request.

This is the crux of the performance issue: wp_load_alloptions() doesn’t know which autoloaded options your current request will actually use — it loads all of them, every time, before your code even has a chance to ask for one.

When Autoload Makes Sense

Autoload is the right call when an option is:

  • Accessed on almost every page load
  • Small in size (a flag, a color, a short string)
  • Core to your plugin’s basic functionality

Autoload usually does not make sense for:

  • Admin-only settings (API keys, license data, advanced configuration)
  • Infrequently accessed data (license checks, update schedules)
  • Large serialized arrays (page builder data, logs, sync caches)
  • Conditional features that aren’t checked on most page loads

WordPress 6.6+: What Actually Changed

Starting with WordPress 6.6 (released July 2024), core made the autoload system explicit, flexible, and easier to manage at scale.

API Parameters Now Accept Booleans — But the Database Stores New String Values

Correction: The original draft framed this as “strings became booleans.” That’s only half the picture. The Options API functions (add_option(), update_option(), wp_set_option_autoload()) now accept a boolean for the $autoload parameter for convenience:

php

// Both of these are valid in WordPress 6.6+
add_option( 'my_option', 'value', '', true );   // will autoload
add_option( 'my_option', 'value', '', false );  // will not autoload

But internally, WordPress doesn’t store a literal boolean in the autoload column. It normalizes the value to one of: on, off, auto, auto-on, or auto-off. The legacy strings yes and no are still accepted everywhere for backward compatibility, but are deprecated as of WordPress 6.7 and new entries are written using the on/off convention instead.

New Intermediate States: auto-on and auto-off

WordPress also introduced “auto” states that let core make the call based on internal heuristics rather than a hard developer decision:

php

wp_set_option_autoload( 'my_option', 'auto-on' );   // WordPress leans toward autoloading
wp_set_option_autoload( 'my_option', 'auto-off' );  // WordPress leans toward not autoloading

These states give core room to optimize without forcing every plugin to make an explicit, permanent choice at registration time.

What about plain auto?

The bare auto state is WordPress’s way of saying “I’ll decide.” In practice, core treats it as leaning toward autoloading — similar to auto-on — but reserves the right to override that decision based on internal heuristics (such as the option’s size exceeding the 150KB threshold). You’ll rarely set this yourself; it exists primarily so core can manage its own options without committing to a hard on or off. For plugin and theme developers, always use an explicit true/false (stored as on/off) rather than relying on auto.

The New Workhorse: wp_set_option_autoload()

Instead of only setting autoload at creation time, you can now change it later — which is the key to cleanup workflows:

php

// Change autoload for an existing option
wp_set_option_autoload( 'my_plugin_setting', false );

// Returns true if the value changed, false if it was already set to that value
if ( wp_set_option_autoload( 'my_plugin_setting', false ) ) {
    // Autoload was changed
} else {
    // Already set to false
}

This is powerful for plugin lifecycle management. When a user deactivates your plugin, you can disable autoload on its options so they stop contributing to bloat while the plugin is inactive:

php

register_deactivation_hook( __FILE__, function() {
    wp_set_option_autoload( 'my_plugin_color', false );
    wp_set_option_autoload( 'my_plugin_timeout', false );
    wp_set_option_autoload( 'my_plugin_cache', false );
} );

For bulk updates, use wp_set_option_autoload_values(), which groups options and updates them — and the cache — in a single pass:

php

wp_set_option_autoload_values( [
    'my_plugin_color'   => false,
    'my_plugin_timeout' => false,
    'my_plugin_cache'   => false,
] );

Core Optimizations in 6.6+

  1. Smarter defaults: the default $autoload parameter changed from true to null, which forces developers to be intentional rather than silently inheriting autoload=true.
  2. Size-based filtering: options whose serialized value exceeds an internal size threshold are automatically excluded from autoload, even if a developer requested it, unless explicitly forced.
  3. Core audit: WordPress 6.7 reviewed core’s own options and removed autoload from a number of unnecessary entries, lowering the baseline for new installs.
  4. Site Health checks: the Site Health dashboard now flags sites whose total autoloaded data exceeds the recommended size.

Correction: The original draft listed the size-based autoload threshold as “typically 400KB.” The actual default is controlled by the wp_max_autoloaded_option_size filter, which defaults to 150KB for a single option’s serialized value. Anything larger is excluded from autoload unless a developer explicitly overrides the filter. This is separate from the Site Health 800KB threshold, which applies to the total combined size of all autoloaded options, not a single option.

Site Health will show “Autoloaded options could affect performance” once the combined size of autoloaded options exceeds 800KB. The 150KB filter governs whether an individual large option is allowed into that pool in the first place.

WordPress Core, Make WordPress Core blog (2024)

For Plugin Developers: The Responsible Approach

At Activation: Set Sensible Defaults

In your plugin’s register_activation_hook, decide intentionally for each option:

php

register_activation_hook( __FILE__, function() {
    // Settings accessed on every page load → autoload makes sense
    add_option( 'my_plugin_enabled', true, '', true );

    // Settings accessed only in admin → no autoload
    add_option( 'my_plugin_api_key', '', '', false );
    add_option( 'my_plugin_license', '', '', false );

    // Cached data accessed conditionally → no autoload
    add_option( 'my_plugin_sync_cache', [], '', false );
    add_option( 'my_plugin_transient_data', [], '', false );

    // Related settings stored as a single array
    add_option( 'my_plugin_settings', [
        'timeout'  => 30,
        'retries'  => 3,
        'format'   => 'json',
        'debug'    => false,
    ], '', false ); // only autoload if accessed on every page
} );

The decision tree is simple: is this option read via get_option() on almost every page load? If yes, autoload it. If no — and for most plugin settings, the answer is no — leave autoload off.

At Deactivation: Disable Autoload

Users often deactivate a plugin temporarily for troubleshooting. Don’t delete their settings — just stop autoloading them:

php

register_deactivation_hook( __FILE__, function() {
    // Disable autoload on all plugin options
    wp_set_option_autoload_values( [
        'my_plugin_enabled'        => false,
        'my_plugin_api_key'        => false,
        'my_plugin_license'        => false,
        'my_plugin_sync_cache'     => false,
        'my_plugin_transient_data' => false,
        'my_plugin_settings'       => false,
    ] );

    // Clear runtime caches
    wp_cache_delete( 'my_plugin_cache_key' );

    // Flush rewrite rules if you registered custom post types
    flush_rewrite_rules();
} );

Why the extra wp_cache_delete() call? wp_set_option_autoload_values() already manages the options cache for you — under the hood it calls wp_cache_delete_multiple() on the changed option names plus wp_cache_delete( 'alloptions', 'options' ) when anything moves to autoload, or it manually strips entries from the cached $alloptions array when moving options off autoload. What it has no way of knowing about is any cache entries your plugin writes outside the options group — for example, a sync cache you stored yourself via wp_cache_set( 'my_plugin_cache_key', $data, 'my_plugin' ). Core only cleans up after itself; anything your plugin cached under its own key or group is your responsibility to invalidate on deactivation.

At Uninstall: Clean Everything

Only when a user completely removes your plugin should you delete its data:

php

// In uninstall.php
register_uninstall_hook( __FILE__, function() {
    delete_option( 'my_plugin_enabled' );
    delete_option( 'my_plugin_api_key' );
    delete_option( 'my_plugin_license' );
    delete_option( 'my_plugin_sync_cache' );
    delete_option( 'my_plugin_transient_data' );
    delete_option( 'my_plugin_settings' );

    global $wpdb;
    $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}my_plugin_data" );
    if ( wp_cache_supports( 'flush_group' ) ) {
        wp_cache_flush_group( 'my_plugin' );
    }
} );

Why wp_cache_delete_group() here too? delete_option() only removes the single cache entry for that option name within the options group — it has no concept of a plugin-wide group. If your plugin has been writing to a persistent object cache (Redis, Memcached, etc.) under its own group, like my_plugin, those entries will keep sitting in the cache indefinitely after uninstall, even though the underlying database rows and tables are gone. wp_cache_delete_group() is the cleanup step that removes everything under that group in one call, so no stale data lingers behind a plugin that no longer exists.

Note: This only has effect if your site uses a persistent object cache that supports group invalidation (e.g. Redis with the wp-redis or Object Cache Pro plugin).

Store Related Settings as One Array

php

// Inefficient: five separate options, five autoload rows
add_option( 'my_plugin_timeout', 30, '', false );
add_option( 'my_plugin_retries', 3, '', false );
add_option( 'my_plugin_format', 'json', '', false );
add_option( 'my_plugin_debug', false, '', false );
add_option( 'my_plugin_version', '2.0', '', false );

// Efficient: one serialized array, one database row
$settings = [
    'timeout' => 30,
    'retries' => 3,
    'format'  => 'json',
    'debug'   => false,
    'version' => '2.0',
];
add_option( 'my_plugin_settings', $settings, '', false );

Retrieve and update cleanly:

php

$settings = get_option( 'my_plugin_settings', [] );
$settings['debug'] = true;
update_option( 'my_plugin_settings', $settings );

Handle Version Migrations

php

register_activation_hook( __FILE__, function() {
    $current_version = get_option( 'my_plugin_version' );

    if ( $current_version && version_compare( $current_version, '2.0', '<' ) ) {
        // Migrate old options to the new structure
        $v2_settings = [
            'color'   => get_option( 'my_plugin_color', '#0073aa' ),
            'timeout' => get_option( 'my_plugin_timeout', 30 ),
        ];
        add_option( 'my_plugin_settings', $v2_settings, '', false );

        // Clean up old individual options
        delete_option( 'my_plugin_color' );
        delete_option( 'my_plugin_timeout' );
        delete_option( 'my_plugin_v1_cache' );

        update_option( 'my_plugin_version', '2.0' );
    }
} );

Multisite: Use the Right Functions

php

// Single-site or site-specific (multisite)
add_option( 'my_option', $value, '', false );
get_option( 'my_option' );
delete_option( 'my_option' );
update_option( 'my_option', $new_value );

// Network-wide (multisite only)
add_network_option( null, 'my_network_option', $value );
get_network_option( null, 'my_network_option' );
delete_network_option( null, 'my_network_option' );

For Theme Developers

Customizer Options Should Rarely Autoload

php

// Avoid: autoloading every customizer setting
register_setting( 'color', array(
    'sanitize_callback' => 'sanitize_hex_color',
    'type'              => 'option',
    'autoload'          => true, // don't do this
) );

// Better: non-autoload, fetch only when rendering
register_setting( 'color', array(
    'sanitize_callback' => 'sanitize_hex_color',
    'type'              => 'option',
    'autoload'          => false,
) );

// In your template
$theme_color = get_option( 'color', '#0073aa' );
echo 'style="color: ' . esc_attr( $theme_color ) . '"';

Prevent Orphaned Options When Switching Themes

php

add_action( 'switch_theme', function() {
    delete_option( 'my_theme_color' );
    delete_option( 'my_theme_logo' );
    delete_option( 'my_theme_font' );
    delete_option( 'my_theme_settings' );
} );

Child Themes: Inherit, Don’t Duplicate

php

// Better: fetch parent settings
$parent_color = get_option( 'parent_theme_color', '#0073aa' );

// Child can override with its own option if needed
$child_color = get_option( 'child_theme_color' );
$color = $child_color ? $child_color : $parent_color;

For Site Owners: Diagnosis and Cleanup

If admin dashboards feel slow, here’s how to check whether autoload bloat is the cause — and fix it safely. The autoload payload is loaded via a standard buffered query on every request, so it’s worth pairing this audit with a broader look at how your site’s queries run — see WordPress Unbuffered Queries with mysqli Streaming if large result sets elsewhere are also a concern.

Check Your Autoload Size

Correction: The original query filtered on autoload='yes' only. Since WordPress 6.6+ stores additional truthy values (on, auto-on, auto), a query limited to 'yes' will under-report the real autoload size on updated sites. Use an IN clause covering every value WordPress treats as “load this”:

php

SELECT
    SUM(LENGTH(option_value)) / 1048576 AS autoloaded_size_mb,
    COUNT(*) AS autoloaded_count
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto');
Autoload SizeStatus
Under 800KBHealthy
800KB – 5MBInvestigate
5MB – 10MBProblematic, cleanup recommended
Over 10MBSevere, cleanup urgent

The WordPress Site Health dashboard surfaces a warning once the combined total crosses roughly 800KB — if you see that warning, the query above will confirm it.

Identify the Top Offenders

sql

SELECT
    option_name,
    ROUND(LENGTH(option_value) / 1024, 2) AS size_kb,
    autoload
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto')
ORDER BY LENGTH(option_value) DESC
LIMIT 20;

Common offenders include page builder data (Elementor, Divi), WooCommerce settings, plugin sync caches and transients stored as regular options, and leftover data from plugins that have since been deactivated or deleted.

Safe Cleanup Workflow

  1. Back up first. wp db export backup-$(date +%Y%m%d_%H%M%S).sql
  2. Test on staging — never edit autoload values directly on production first.
  3. Use a plugin for auditing if you’re not comfortable with SQL — tools like Advanced Database Cleaner provide a guided UI.
  4. Check for orphaned plugin data by comparing option name prefixes against your currently active plugins.
  5. Disable autoload before deleting if you’re unsure an option is still needed — this removes the overhead without destroying data.
  6. Verify impact by re-checking Site Health and re-running the size query after cleanup.
sql

-- Find orphaned options from a deactivated plugin (example: Jetpack)
SELECT option_name, ROUND(LENGTH(option_value) / 1024, 2) AS size_kb, autoload
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto')
AND option_name LIKE 'jetpack_%'
ORDER BY LENGTH(option_value) DESC;

Correction: The “disable autoload” cleanup query has been updated in two ways — the WHERE clause now matches all current “on” states with an IN clause, and the value being set has been changed from the deprecated 'no' to the current standard, 'off':

sql

UPDATE wp_options
SET autoload = 'off'
WHERE option_name IN (
    'old_plugin_setting_1',
    'old_plugin_setting_2',
    'old_plugin_cache'
)
AND autoload IN ('yes', 'on', 'auto-on', 'auto');

A direct UPDATE against wp_options bypasses WordPress’s object cache, so the alloptions cache entry can remain stale until it naturally expires or is flushed. Where possible, prefer wp_set_option_autoload_values() in code, or the WP-CLI commands below, since both update the cache correctly as part of the operation.

WP-CLI for Developers

shellscript

# Check total autoloaded payload size
wp eval 'echo "Autoloaded data: " . round( strlen( serialize( wp_load_alloptions() ) ) / 1048576, 2 ) . " MB\n";'

# Disable autoload for a specific option (accepts on/off, yes/no)
wp option set-autoload deprecated_plugin_option off

# Delete an option entirely
wp option delete deprecated_plugin_option

# List autoloaded options matching a pattern
wp option list --autoload=on --search="old_plugin*" --format=csv

Correction: The original draft used wp option list --search="old_plugin%" --format=csv without filtering by autoload status, which would also list non-autoloaded options that don’t contribute to the issue. Adding --autoload=on scopes the list to options that are actually part of the autoload payload.

Real-World Case Studies

The diagnostic query pattern above shows up again and again in three common scenarios. In each case, the fix is the same: disable autoload on the offending options rather than deleting them, using 'off' and an IN clause that matches every current “on” state.

ScenarioTypical Autoload BloatUsual Culprit
Elementor-built sites (100+ pages)3–5MBSerialized page layout and cache data
WooCommerce stores2–5MBGateway configs, shipping rules, tax tables
Multisite network (50 sites)Up to 250MB network-wideThe same per-site bloat, multiplied

Elementor: Find and Disable Layout/Cache Autoload

sql

SELECT option_name, ROUND(LENGTH(option_value) / 1048576, 2) AS size_mb
FROM wp_options
WHERE option_name LIKE '%elementor%'
AND autoload IN ('yes', 'on', 'auto-on', 'auto')
ORDER BY LENGTH(option_value) DESC;

Elementor fetches its cache and page data on demand, so disabling autoload on these rows doesn’t break anything — it just stops them from riding along on every request.

WooCommerce: Audit Gateway and Settings Options

sql

SELECT option_name, ROUND(LENGTH(option_value) / 1024, 2) AS size_kb
FROM wp_options
WHERE option_name LIKE 'woocommerce_%'
AND autoload IN ('yes', 'on', 'auto-on', 'auto')
ORDER BY LENGTH(option_value) DESC
LIMIT 10;

Recent WooCommerce versions (8.x and above) have been progressively reducing unnecessary autoloaded options — each release tends to clean up a few more. On older installs especially, disable autoload on gateway configs and transient-style cache options that don’t need to be present on every page.

Multisite: One Update, Network-Wide

A 50-site network where each site carries 5MB of autoload bloat adds up to roughly 250MB across the network. Unlike single-site, each subsite has its own prefixed options table (wp_2_options, wp_3_options, and so on), so the cleanup needs to run once per site.

Option 1 — SQL per site:

sql
-- Main site (site 1)
UPDATE wp_options
SET autoload = 'off'
WHERE option_name LIKE 'elementor_%'
AND autoload IN ('yes', 'on', 'auto-on', 'auto');

-- Sub-sites (repeat for each site ID)
UPDATE wp_2_options SET autoload = 'off' WHERE option_name LIKE 'elementor_%' AND autoload IN ('yes', 'on', 'auto-on', 'auto');
UPDATE wp_3_options SET autoload = 'off' WHERE option_name LIKE 'elementor_%' AND autoload IN ('yes', 'on', 'auto-on', 'auto');

Option 2 — WP-CLI loop (recommended for large networks):

shellscript
wp site list --field=url | while read url; do
  wp option list --search="elementor_*" --autoload=on --field=option_name --url="$url" | while read option; do
    wp option update "$option" "$(wp option get "$option" --url="$url")" --autoload=off --url="$url"
  done
done

Correction: This now sets autoload = ‘off’ (the current standard) instead of the deprecated ‘no’, and matches against every value WordPress treats as “load this” via IN (‘yes’, ‘on’, ‘auto-on’, ‘auto’) rather than only ‘yes’. The WP-CLI loop is preferred for networks of 10+ sites as it avoids needing direct database access and handles the per-site table prefix automatically.

Best Practices for Managing Autoload

  • Performance: default new options to autoload=false and only opt in when an option is read on nearly every request.
  • Security: never autoload API keys, tokens, or license data — autoloaded values sit in the $alloptions array in memory for the entire request lifecycle.
  • Scalability: consolidate related settings into a single serialized array option rather than dozens of individual rows.
  • Accessibility/maintainability: document which options your plugin registers and their autoload status so future maintainers (and uninstall routines) can find them.
  • SEO/Site Health: keep total autoloaded data under 800KB to avoid the Site Health “Autoloaded options could affect performance” warning, which can also be a signal search engines’ Core Web Vitals crawlers pick up on via slow TTFB.
  • Coding standards: use wp_set_option_autoload() / wp_set_option_autoload_values() instead of raw SQL whenever possible, since these keep the object cache consistent.

Common Mistakes with Autoload

  • Leaving the default autoload value unset and assuming WordPress will “figure it out” — explicit is better than implicit.
  • Autoloading large serialized arrays (page builder layouts, sync logs, full API responses) “just in case.”
  • Never cleaning up on deactivation or uninstall, leaving orphaned autoloaded rows behind indefinitely.
  • Running raw SQL UPDATE statements against autoload without accounting for the object cache, leading to confusing “it didn’t work” results.
  • Checking autoload size with a query that only matches autoload='yes' on a WordPress 6.6+ site, which under-counts options stored as on, auto-on, or auto.
  • Confusing the 800KB Site Health total threshold with the 150KB per-option size threshold — they govern different things.
  • Storing five related settings as five separate options instead of one array, multiplying autoload row overhead unnecessarily.

Summary & Next Steps

Autoload is invisible but powerful — a small change in how plugins and themes manage it is often the difference between a fast and a sluggish WordPress install. WordPress 6.6+ gave everyone the tools to fix it: on/off/auto/auto-on/auto-off states, a 150KB per-option size filter, an 800KB Site Health threshold, and a dynamic API (wp_set_option_autoload(), wp_set_option_autoload_values()) for changing autoload after the fact. The responsibility now splits cleanly by role:

  • Plugin & theme developers: default new options to autoload=false, set intentional defaults at activation, disable autoload at deactivation, group related settings into one array option, and migrate to the WordPress 6.6+ API.
  • Site owners: run the autoload diagnostic query to know your baseline, investigate anything over 800KB–5MB, treat Site Health warnings as an audit trigger, and back up before any cleanup.
  • Hosts & agencies: bake an autoload audit into optimization checklists, flag the impact to clients, and re-check sites after major plugin updates — test versions often leave options behind.

Next steps: run the autoload size query (using autoload IN ('yes','on','auto-on','auto'), not just ='yes') on your own site, identify the top offenders, and audit your plugins’ activation/deactivation hooks against the patterns above.

Database and PHP-level performance are only part of the picture — modern WordPress sites are increasingly being crawled and queried by AI agents and assistants as well as humans. If that’s on your roadmap, How to Make Your Site AI Agent Ready covers the well-known endpoints and structural changes worth adding alongside this kind of backend cleanup.

Resources

Frequently Asked Questions

The autoload column controls whether WordPress loads an option's value into memory on every page request, before any page-specific code runs. Options marked to autoload are cached together in an array called $alloptions and served without a separate database query. This avoids many small queries but means every autoloaded option adds to the memory and deserialization cost of every single request, whether or not that request actually uses the value.
Under 800KB of combined autoloaded data is generally considered healthy and is the threshold WordPress's Site Health check uses before showing a warning. Between roughly 800KB and 5MB is worth investigating, 5–10MB is problematic and usually needs cleanup, and anything over 10MB should be treated as urgent. Sites with many plugins, page builders, or years of accumulated leftover data frequently exceed these numbers without anyone noticing until the admin dashboard starts feeling slow.
WordPress 6.6 introduced new autoload states beyond the old yes/no pair, including on, off, auto, auto-on, and auto-off, and made the Options API functions accept booleans for convenience. It also added wp_set_option_autoload() and wp_set_option_autoload_values(), which let developers change an option's autoload status after it has already been created, and introduced a size-based filter that automatically excludes very large option values from autoload by default.
As of WordPress 6.6 and later, the autoload column accepts on, off, auto, auto-on, and auto-off, in addition to the legacy yes and no values kept for backward compatibility. Options stored as on, auto-on, auto, or the legacy yes are included in the autoload payload that loads on every request, while off, auto-off, and the legacy no are excluded. Any SQL audit written for a WordPress 6.6+ site should check for all of these "on" variants, not just yes.
In most cases, no. Plugin settings should only autoload if they're read via get_option() on nearly every page load, such as a feature flag checked on every request. Admin-only settings like API keys and license data, infrequently used configuration, and large serialized arrays such as page builder layouts or sync caches should be stored with autoload disabled. Grouping related settings into a single array option also reduces the number of autoloaded rows compared to many individual options.
Start by backing up your database, then run a query that sums the size of options where autoload is on, auto-on, auto, or the legacy yes, and order the results to find the largest entries. Cross-check the largest option names against your active plugins to identify orphaned data from plugins that are no longer in use. Where you're unsure whether an option is still needed, disable its autoload value rather than deleting it outright, then re-check Site Health to confirm the warning clears.
Run a query that lists autoloaded option names ordered by size, then look at the prefixes of the largest entries — most plugins use a consistent prefix for their option names, such as wpseo_ for Yoast SEO or woocommerce_ for WooCommerce. If a prefix corresponds to a plugin that's deactivated or no longer installed, that data is orphaned and safe to disable from autoload. Page builder plugins like Elementor and Divi are frequent top offenders because they store large layout data as serialized options.
This is a classic symptom of autoload bloat. Frontend pages are often served from page caching or a CDN, which hides the cost of loading the autoload payload on every request. The WordPress admin dashboard, however, is almost entirely uncached, so every click pays the full cost of loading and deserializing every autoloaded option. If your autoload size is in the multi-megabyte range, expect the admin and any uncached pages, such as WooCommerce checkout, to feel noticeably slower than the cached frontend.

Want to discuss an architecture challenge?

If any of these articles sparked questions about your own systems — scaling, performance, or API design — I'm happy to talk through it.

What This Blog Covers