WordPress transients are one of the most misunderstood parts of the WordPress database. They are designed to be temporary cached data with an expiration time, but in practice, expired transients accumulate in the wp_options table and silently degrade performance. On a typical WordPress site with 20+ plugins, the options table can contain hundreds or thousands of expired transient rows that serve no purpose and add weight to every autoloaded query.
This guide explains how the WordPress Transient API works internally, where transients are stored, how to find and delete expired transients safely, and how to prevent transient bloat from recurring. If you have not already audited your wp_options table, start with our guide on fixing wp_options autoload bloat, transients are often the largest contributor.
How the WordPress Transient API Works
The Transient API provides three core functions for storing temporary data:
set_transient( $key, $value, $expiration ), stores data with a TTL (time-to-live) in secondsget_transient( $key ), retrieves the data if it has not expireddelete_transient( $key ), removes the transient manually
When you call set_transient( 'my_data', $value, HOUR_IN_SECONDS ), WordPress creates two rows in the wp_options table:
The _transient_timeout_ row stores a Unix timestamp representing when the transient expires. When get_transient() is called, WordPress checks this timestamp against time(). If the current time exceeds the stored timestamp, the transient is considered expired, WordPress deletes both rows and returns false.
The Lazy Deletion Problem
Here is the critical issue: WordPress uses lazy deletion for transients. Expired transients are only deleted when something calls get_transient() for that specific key. If a plugin sets a transient and then never reads it again (common with one-time API checks, update notifications, or A/B test data), the expired rows stay in the database indefinitely.
WordPress does have a built-in cleanup mechanism, delete_expired_transients(), which runs on the wp_scheduled_delete cron event (daily). However, this function only targets database-stored transients and can miss orphaned timeout rows or transients set by plugins that were later deactivated.
This query joins transient value rows with their corresponding timeout rows and deletes both where the timeout has passed. It works, but it runs only once per day and only when WP-Cron fires reliably.
Where Transients Are Actually Stored
Transient storage depends on your caching setup:
| Caching Setup | Transient Storage | Expired Cleanup |
|---|---|---|
| No object cache (default) | wp_options table | Lazy deletion + daily cron |
| Object cache (Redis/Memcached) | In-memory cache | Automatic TTL expiration |
| Object cache + persistent | In-memory with disk fallback | Automatic TTL expiration |
When an external object cache is active (wp_using_ext_object_cache() returns true), WordPress stores transients in the object cache instead of the database. Redis and Memcached handle TTL expiration natively, expired keys are evicted automatically. This eliminates the database bloat problem entirely.
Key insight: If you are running Redis or Memcached, your transients are not in wp_options. The delete_expired_transients() function skips execution when an object cache is detected. However, if you recently added an object cache to an existing site, the old database transients may still be sitting in wp_options from before the cache was enabled.
How to Find Expired Transients
Method 1: SQL Query (phpMyAdmin or MySQL CLI)
Count all transients in your options table:
Method 2: WP-CLI
WP-CLI provides the most convenient way to audit transients:
Method 3: PHP Snippet (For Debugging)
Add this temporarily to a debug log or mu-plugin to audit transients on page load:
How to Delete Expired Transients Safely
Option 1: WP-CLI (Recommended)
The safest approach for production sites:
wp transient delete --expired calls the core delete_expired_transients() function with $force_db = true, ensuring it runs even when an object cache is present. This is the correct way to clean up legacy database transients after enabling Redis or Memcached.
Option 2: SQL Query (Direct Database)
For situations where WP-CLI is not available:
Always back up your database before running direct SQL deletes. Use SELECT COUNT(*) first to verify the scope of what will be deleted.
Option 3: WordPress Hook (Automated Cleanup)
Force more frequent transient cleanup by hooking into a custom cron schedule:
Object Cache: The Permanent Fix
The root cause of transient database bloat is storing temporary data in a permanent storage layer (MySQL). The permanent fix is enabling a persistent object cache:
- Redis: Most managed WordPress hosts (Kinsta, WP Engine, Cloudways) offer Redis. Install the
redisPHP extension and drop aobject-cache.phpdrop-in (via the Redis Object Cache plugin or manually). - Memcached: An alternative in-memory cache. Less feature-rich than Redis but effective for transient caching.
With an object cache active:
- New transients are stored in memory, not the database
- TTL expiration is handled automatically by Redis/Memcached
- No orphaned rows, no lazy deletion, no cron dependency
- Transient reads become sub-millisecond operations instead of database queries
After enabling an object cache, clean up the legacy database transients:
Common Transient Offenders
Certain plugins and patterns generate excessive transients. Here are the most common offenders and how to identify them:
| Transient Pattern | Source | Impact |
|---|---|---|
_transient_feed_* | WordPress core RSS feed cache | Low, small data, expires properly |
_transient_plugin_slugs | Plugin update checks | Low, single row |
_transient_wc_* | WooCommerce product/session caches | High, can generate thousands of rows |
_transient_jetpack_* | Jetpack module caches | Medium, dozens of rows per module |
_transient_timeout_twt_* | Twitter/social feed plugins | High, often orphaned after plugin removal |
_transient_gf_form_* | Gravity Forms | Medium, form cache entries |
_transient_external_ip_* | Various security/analytics plugins | Low, but can pile up |
To identify the biggest offenders on your site:
Transients vs Options: When to Use Each
A common mistake is using transients for data that should be a regular option, or vice versa. Here is the distinction:
| Use Case | Storage | Why |
|---|---|---|
| Plugin settings | get_option() | Persistent, no expiration needed |
| Cached API response | get_transient() | Temporary, should expire and refresh |
| External data feed | get_transient() | Reduces API calls, handles rate limits |
| User preferences | get_user_meta() | Per-user, persistent |
| Expensive query result | get_transient() | Cache computed data, regenerate on expiration |
| Feature flags | get_option() | Persistent, admin-controlled |
The rule is simple: if the data must survive indefinitely, use an option. If the data can be regenerated and should expire, use a transient. Transients with no expiration ($expiration = 0) are stored permanently with autoload set to yes, essentially becoming options that pollute autoloaded data. Avoid this pattern.
Monitoring and Prevention
Set up ongoing monitoring to catch transient bloat before it impacts performance:
For sites without WP-CLI access, add a simple admin notice:
Performance Impact: Before and After
On a production WooCommerce site with 47 plugins, we measured the following impact from transient cleanup:
| Metric | Before Cleanup | After Cleanup | Improvement |
|---|---|---|---|
| Transient rows in wp_options | 4,218 | 312 | -93% |
| wp_options table size | 48 MB | 12 MB | -75% |
| Autoloaded data size | 3.2 MB | 1.8 MB | -44% |
| Average TTFB | 890ms | 520ms | -42% |
| Admin dashboard load | 2.4s | 1.6s | -33% |
The biggest gains came from removing WooCommerce session transients and orphaned transients from deactivated plugins. After adding Redis, transient-related database queries dropped to zero.
Checklist: Transient Maintenance
- Audit transient count monthly:
wp transient list --format=count - Delete expired transients weekly:
wp transient delete --expired - Enable Redis or Memcached to eliminate database transients entirely
- After enabling object cache, run
wp transient delete --allonce to clean legacy rows - After deactivating plugins, check for orphaned transients from those plugins
- Monitor
wp_optionstable size as part of routine database maintenance - Never set transients with
$expiration = 0, useget_option()instead - For WooCommerce sites, clean
_transient_wc_*rows periodically
Frequently Asked Questions
Is it safe to delete all WordPress transients?
Yes. Transients are cached data by design, they will be regenerated automatically when plugins or WordPress core request them. Deleting all transients (wp transient delete --all) causes a temporary performance dip as caches are rebuilt, but no data is permanently lost. On high-traffic sites, run this during low-traffic hours to avoid a thundering herd of cache regeneration queries.
Why does WordPress store transients in the database instead of memory?
WordPress was designed to work on any hosting environment, including shared hosting without Redis or Memcached. The database is the universal fallback. When an object cache is available, WordPress automatically routes transients to the in-memory cache instead. The database storage is a compatibility mechanism, not the preferred approach.
How often should I clean expired transients?
WordPress runs delete_expired_transients() daily via WP-Cron. For most sites, this is sufficient. For WooCommerce sites or sites with 30+ plugins, running wp transient delete --expired weekly via server cron provides better control. The permanent solution is enabling an object cache so transients never reach the database.
Do transients affect WordPress autoload performance?
Transients with an expiration time are stored with autoload = no, so they do not affect autoloaded data size directly. However, transients without an expiration ($expiration = 0) are stored with autoload = yes, directly impacting every page load. Additionally, a bloated wp_options table (from thousands of transient rows) slows down all queries against that table, including the autoload query. See our wp_options autoload guide for details.
What is the difference between transients and the object cache?
The Transient API is a WordPress abstraction layer. The object cache is the underlying storage mechanism. Without Redis or Memcached, transients use the database. With an object cache, transients use in-memory storage with native TTL support. The Transient API provides a consistent developer interface regardless of the storage backend, your code calls set_transient() the same way either way.
database cleanup WordPress Database Optimization WordPress Performance wp_options WP-CLI
Last modified: February 24, 2026