Key Takeaways
The autoloaded data in wp_options loads on every single page request, cached or not
Most WordPress sites have 2-10x more autoloaded data than they need
Expired transients are always safe to delete and often account for megabytes of waste
You need SSH access to run these queries; S/FTP won't work for direct database operations
Always run OPTIMIZE TABLE after a mass delete to reclaim disk space
The table nobody thinks about
Every WordPress site has a table called wp_options. It stores your site title, your permalink structure, your active plugins, your theme settings, and about a thousand other things.
It also stores garbage.
Plugins write to wp_options when they're installed. They write to it when they run. And most of them never clean up after themselves when you deactivate or delete them. After a year or two, this table becomes a dumping ground for expired caches, orphaned settings, and serialized blobs of data from plugins you forgot you ever installed.
Here's why that matters: WordPress loads a chunk of this table on every single page request. Not just the data it needs. Everything marked as "autoload." On a healthy site, that's maybe 500KB. On the sites I typically get called in to fix, it's 5-15MB. I've seen 40MB.
That autoloaded data gets pulled from MySQL, loaded into PHP memory, and processed before WordPress even starts thinking about your actual page content. It's the silent tax on every request.
Before you start
Back up your database. Full stop. Not a plugin backup, not a WordPress export. A proper mysqldump or equivalent from your hosting panel. If something goes wrong, you need to be able to restore the entire wp_options table in minutes.
mysqldump -u your_user -p your_database wp_options > wp_options_backup.sqlYou need SSH access. I run these queries directly on the database through an SSH connection to the server. S/FTP won't work here because you need access to the MySQL command line (or MariaDB, same commands). If your host doesn't give you SSH, you can run these same queries through phpMyAdmin or Adminer from your hosting control panel.
To connect to MySQL from SSH:
mysql -u your_user -p your_databaseYou'll be prompted for the password, and then you're in.
I'm recording a full video walkthrough of this process. I'll embed it here once it's ready.
Step 1: measure the damage
Before deleting anything, you need to know what you're dealing with. This query gives you four numbers: total rows, how many are autoloaded, the total data size, and how much of that is autoloaded.
SELECT
COUNT(*) as total_rows,
SUM(CASE WHEN autoload IN ('yes','on') THEN 1 ELSE 0 END) as autoloaded_rows,
ROUND(SUM(LENGTH(option_value))/1024/1024, 2) as total_data_mb,
ROUND(SUM(CASE WHEN autoload IN ('yes','on') THEN LENGTH(option_value) ELSE 0 END)/1024/1024, 2) as autoloaded_mb
FROM wp_options;On a healthy site, you'd expect something like:
| Metric | Healthy | Bloated |
|---|---|---|
| total_rows | 200-500 | 2,000-50,000+ |
| autoloaded_rows | 100-200 | 500-5,000+ |
| total_data_mb | 1-3 MB | 10-100+ MB |
| autoloaded_mb | 0.3-0.8 MB | 2-40+ MB |
If your autoloaded_mb is over 1MB, there's work to do. If it's over 5MB, your site is dragging a sled of dead weight on every request.
Write these numbers down. You'll compare them against the results after cleanup.
Step 2: find the biggest autoloaded offenders
Now you need to see what is eating up that space. This pulls the top 30 largest autoloaded options:
SELECT
option_name,
ROUND(LENGTH(option_value)/1024, 2) as size_kb,
autoload
FROM wp_options
WHERE autoload IN ('yes','on')
ORDER BY LENGTH(option_value) DESC
LIMIT 30;This is where patterns start to emerge. Across the sites I work on, the usual suspects are:
_site_transient_update_*: WordPress update check caches. Often hundreds of KB each.rewrite_rules: Can balloon on sites with custom post types and WooCommerce. Usually 100-500KB. Don't delete this one; it regenerates when you flush permalinks.*_user_roles: Grows large if plugins add custom roles. Usually fine to leave.- Serialized plugin settings: Page builders (Elementor, Divi, WPBakery) and SEO plugins (Yoast, Rank Math) store massive blobs of serialized data here.
action_scheduler_*: WooCommerce's background task runner. If you're seeing large entries here, that's a separate problem worth investigating.cron: The WordPress cron array. Grows when plugins register cron jobs and don't clean them up on deactivation.
Don't delete anything yet. Just note which option names are taking up space and which plugins they belong to.
Step 3: find orphaned plugin data by prefix
Individual large rows are one problem. Hundreds of small rows from the same plugin are another. This groups options by their name prefix and shows you which plugins are leaving the most entries:
SELECT
SUBSTRING_INDEX(option_name, '_', 3) as prefix,
COUNT(*) as cnt,
ROUND(SUM(LENGTH(option_value))/1024, 2) as total_kb
FROM wp_options
GROUP BY prefix
HAVING cnt > 10
ORDER BY cnt DESC
LIMIT 30;I see the same offenders across almost every site:
_transient_/_site_transient_: Temporary cached data. Often thousands of rows.wc_/woocommerce_: WooCommerce settings. Legitimate, but check for stale session data.elementor_: Elementor stores CSS caches and revision data here.wordfence_: Wordfence writes a lot of scan data and firewall rules to options.redirection_: The Redirection plugin stores log data here if misconfigured.itsec_: iThemes Security/Solid Security. Stores notification logs.- Prefixes from plugins you've already deleted: This is the real find. If you see a prefix for a plugin that's no longer active, that's dead weight.
The "plugins you've already deleted" category is the low-hanging fruit. That data is doing nothing. The plugin is gone. The options stay behind forever.
Step 4: count autoloaded transients
Transients are WordPress's built-in caching mechanism. Plugins use them to store temporary data so they don't have to recompute or re-fetch it on every request. The problem is twofold: many transients are set to autoload (loaded on every page), and many are never cleaned up after they expire.
SELECT
COUNT(*) as transient_count,
ROUND(SUM(LENGTH(option_value))/1024/1024, 2) as transient_mb
FROM wp_options
WHERE option_name LIKE '_transient_%'
AND autoload IN ('yes','on');On a WooCommerce site I cleaned up last month, this returned 4,200 transients totaling 8.3MB. All autoloaded. All loading on every single page view. The site owner had no idea why their checkout was slow.
Step 5: identify expired transients
Expired transients are the easiest win. They're cached values that have passed their expiration time but haven't been garbage-collected by WordPress. They sit in the table doing nothing except adding weight.
SELECT COUNT(*) as expired_transients
FROM wp_options a
JOIN wp_options b ON b.option_name = CONCAT('_transient_timeout_', SUBSTRING(a.option_name, 12))
WHERE a.option_name LIKE '_transient_%'
AND a.option_name NOT LIKE '_transient_timeout_%'
AND b.option_value < UNIX_TIMESTAMP();This query joins each transient with its timeout value and checks whether the timeout has passed. If the number is anything above zero, you have free cleanup waiting.
I've seen this return 10,000+ expired transients on sites running WooCommerce with several marketing plugins. WordPress is supposed to clean these up, but the garbage collection only runs when someone happens to request a specific expired transient. If nobody requests it, it sits there indefinitely.
Step 6: delete the dead weight
Now that you've audited everything, it's time to clean. Start with expired transients because they're always safe to remove. WordPress regenerates transients as needed.
Delete expired transients
DELETE a, b FROM wp_options a
JOIN wp_options b ON b.option_name = CONCAT('_transient_timeout_', SUBSTRING(a.option_name, 12))
WHERE a.option_name LIKE '_transient_%'
AND a.option_name NOT LIKE '_transient_timeout_%'
AND b.option_value < UNIX_TIMESTAMP();This deletes both the transient and its corresponding timeout row in one operation.
Delete orphaned plugin data
For options left behind by plugins you've already uninstalled, use a targeted LIKE pattern. Replace the prefix with whatever you identified in Step 3:
DELETE FROM wp_options WHERE option_name LIKE 'old_plugin_prefix_%';Be specific with these deletes. Don't guess. If you're not 100% sure a prefix belongs to a deactivated plugin, check first. Search the prefix in your /wp-content/plugins/ directory. If nothing comes up, it's orphaned.
Some examples from real cleanups I've done:
-- Plugin was uninstalled months ago, 847 rows left behind
DELETE FROM wp_options WHERE option_name LIKE 'wpseo_%';
-- Wordfence removed, scan data still sitting in options
DELETE FROM wp_options WHERE option_name LIKE 'wordfence_%';
-- Old social sharing plugin, 200+ rows of cached share counts
DELETE FROM wp_options WHERE option_name LIKE 'mashsb_%';Only run these if you've confirmed the plugin is gone and you're not planning to reinstall it. If you reinstall the plugin later, it will recreate its options from scratch.
Step 7: optimize the table
After deleting hundreds or thousands of rows, MySQL doesn't automatically reclaim that disk space. The table has holes in it. OPTIMIZE TABLE rewrites the table file, defragments it, and updates the index statistics:
OPTIMIZE TABLE wp_options;This can take a few seconds on small tables or a minute or more on large ones. On InnoDB (which is the default engine for modern MySQL/MariaDB), this effectively performs an ALTER TABLE ... FORCE which rebuilds the table and reclaims space.
Don't skip this step. I've seen people delete 5,000 rows and wonder why the table is still the same size on disk. This is why.
Step 8: verify the results
Run the same audit query from Step 1 to see the difference:
SELECT
COUNT(*) as total_rows,
SUM(CASE WHEN autoload IN ('yes','on') THEN 1 ELSE 0 END) as autoloaded_rows,
ROUND(SUM(LENGTH(option_value))/1024/1024, 2) as total_data_mb,
ROUND(SUM(CASE WHEN autoload IN ('yes','on') THEN LENGTH(option_value) ELSE 0 END)/1024/1024, 2) as autoloaded_mb
FROM wp_options;Compare the before and after. On a recent client site (WooCommerce store, running for about 3 years with 20+ plugins installed and removed over time):
| Metric | Before | After |
|---|---|---|
| total_rows | 14,832 | 1,247 |
| autoloaded_rows | 3,891 | 412 |
| total_data_mb | 22.4 MB | 2.1 MB |
| autoloaded_mb | 8.7 MB | 0.6 MB |
The autoloaded data dropped from 8.7MB to 0.6MB. That's 8.1MB less data loaded into PHP memory on every single page request. The site's TTFB dropped from 1.8s to 0.4s on uncached pages. The admin panel felt like a different site.
What not to delete
Not everything in wp_options is disposable. Some rows are load-bearing. Delete them and your site breaks. Here's what to leave alone:
siteurlandhome: Your site's URL. Delete these and WordPress won't know where it lives.active_plugins: The serialized list of active plugins. Delete this and every plugin deactivates.templateandstylesheet: Your active theme. Delete these and you get the default theme.rewrite_rules: Your permalink structure. If this gets too large, flush permalinks from Settings > Permalinks rather than deleting the row.cron: The scheduled tasks array. If it's bloated, the fix is to deregister the cron jobs properly, not delete the row.widget_*: Your widget configurations. Delete these and your sidebars go blank.theme_mods_*: Your Customizer settings.
When in doubt, don't delete. Copy the option_name and search for it. If it belongs to an active plugin or WordPress core, leave it.
Preventing the bloat from coming back
Cleaning up once is good. Keeping it clean is better.
Audit your plugin stack. Every plugin you install writes to wp_options. Every plugin you remove leaves its data behind. Before installing a plugin, ask if you actually need it. Before removing one, check if it has a "clean uninstall" option or a deactivation hook that removes its data.
Use a transient cleaner. The free "Transient Cleaner" plugin by Developer's Best Friend or the WP-CLI command wp transient delete --expired can be scheduled to run weekly via cron. On sites where I manage the server, I add this to a cron job:
wp transient delete --expired --path=/var/www/your-siteMonitor autoload size. If you have WP-CLI access, this one-liner gives you the autoloaded total:
wp db query "SELECT ROUND(SUM(LENGTH(option_value))/1024/1024, 2) as autoloaded_mb FROM wp_options WHERE autoload IN ('yes','on');"Run it monthly. If autoloaded_mb creeps above 1MB, investigate.
Turn off autoload for large options that don't need it. Some plugins autoload data that only gets used on specific admin pages. If you identify large autoloaded options that aren't needed on every page, you can disable autoload:
UPDATE wp_options SET autoload = 'no' WHERE option_name = 'some_large_option';Only do this if you're confident the option isn't required on frontend page loads. Test afterward.
The bigger picture
The wp_options table is usually the first thing I look at when a site owner says "WordPress is slow." It's not the only table that causes problems (wp_postmeta and the Action Scheduler tables are their own disasters), but it's the one with the highest impact-to-effort ratio. Fifteen minutes of SQL and you can cut page generation time in half.
If your site has been running for more than a year and you've never cleaned this table, there's almost certainly dead data in it. The process above is the exact one I run across client sites. No plugins, no abstractions, just SQL.
Related reading
- How to make a WordPress site load in under 2 seconds. The full speed checklist, including the caching and image optimization that complement database cleanup.
- Modern WordPress hosting stack: Docker, cloud, faster sites. The server infrastructure that gets you sub-500ms TTFB.
- Scaling WooCommerce: what breaks after 10,000 visitors. When database cleanup isn't enough and you need architectural changes.
- Cheap website hosting costs more than you think. If your TTFB is still high after cleaning wp_options, your server might be the bottleneck.

Written by
Barry van Biljon
Full-stack developer specializing in high-performance web applications with React, Next.js, and WordPress.
Ready to Get Started?
Have questions about implementing these strategies? Our team is here to help you build high-performance web applications that drive results.
