Skip to content

How the WordPress transient API works, and when wp transient delete actually helps

WordPress ships with a built-in way to store data temporarily — save something for a fixed window of time, and it stops being valid once that window closes. This is the transient API, and both WordPress core and countless plugins lean on it to cache things like external API responses or the results of expensive calculations. It’s a genuinely useful mechanism, but used without understanding how it actually behaves, expired entries can pile up and quietly bloat the database.

Note: the transient API is WordPress core’s name for a small set of PHP functions — set_transient(), get_transient(), delete_transient() — built around the idea of a cache entry with an expiration.

How a transient actually works

Saving a transient means specifying three things: a value, a key, and an expiration in seconds.

set_transient('weather_data', $api_response, 3600); // cache for one hour

Where that value actually gets stored depends on the site’s setup:

  • Default setup (most shared hosting environments): it lands in the wp_options table as a row named _transient_<key>, with a matching _transient_timeout_<key> row holding the expiration
  • With a persistent object cache (a plugin backed by Redis or Memcached): the value goes to that cache layer instead of wp_options

When get_transient() is called, WordPress compares the timeout value against the current time and returns false if the entry has expired. At that point, the design intends for the stale row to be cleaned up automatically — but that cleanup isn’t as reliable as it sounds.

Why expired entries stick around

In theory, an expired transient should disappear. In practice, wp_options can accumulate a large number of long-expired rows. Two things typically cause this:

  1. get_transient() is never called again for that key. The automatic cleanup described above is passive — it only fires when something actually tries to read the value and finds it expired. It isn’t an active sweep. If a plugin sets a value once and never checks it again, the row just sits there past its expiration indefinitely.
  2. WP-Cron’s garbage collection doesn’t run. WordPress core schedules a periodic cleanup job (wp_scheduled_delete, among others) to purge expired data. But WP-Cron is a pseudo-cron that only fires on page visits, so on a site with very light traffic, that cleanup can go a long time without actually executing.

What makes this worse is that most _transient_-prefixed options are saved with autoload set to yes — meaning WordPress loads them into memory on every single page request, whether or not that page needs them. A handful of stale rows is harmless. Thousands of them, and every page load starts carrying that weight, even pages that never touch the cached values in question.

Inspecting and cleaning up with wp transient

WP-CLI ships a dedicated set of subcommands for this:

# List everything currently stored as a transient
wp transient list

# Delete one specific key
wp transient delete weather_data

# Delete only the ones that have already expired
wp transient delete --expired

# Delete everything, expired or not
wp transient delete --all

The distinction between --expired and --all matters. --expired only touches rows that are already past their timeout, so running it has essentially no side effects. --all wipes out live, still-valid cache entries too — which means plugins have to recompute or re-fetch that data immediately afterward, possibly hitting external APIs all at once and causing a brief slowdown right after you run it. Unless you’re deliberately forcing a full cache reset while troubleshooting something specific, --expired is the safer default for routine maintenance.

Note: on a site running a persistent object cache (Redis or Memcached), the wp transient commands operate against that cache layer instead of wp_options. Checking wp_options directly — for example with wp db query "SELECT COUNT(*) FROM wp_options WHERE option_name LIKE '_transient_%'" — only reflects reality on sites without a persistent object cache in front of it.

When it’s worth running

  • As a first check when a site feels generally slow. Run wp option list --autoload=on --search='_transient_%' to see how many autoloaded transient rows exist. Thousands of rows is a reasonable signal that wp transient delete --expired is worth trying.
  • After swapping out or bulk-removing plugins. Some plugins don’t bother calling delete_transient() for their own keys before being uninstalled, leaving orphaned _transient_ rows with no code left to ever read or clean them up.
  • As part of routine maintenance. Folding transient cleanup into the same cycle as wp db check / wp db optimize turns autoload bloat into something checked on a predictable schedule, rather than something that only gets noticed once it’s already affecting page load times.

Summary

What you want to do Command
List everything currently cached as a transient wp transient list
Delete one specific key wp transient delete <key>
Delete only expired entries (safe) wp transient delete --expired
Delete everything, including live cache (has side effects — use for troubleshooting) wp transient delete --all
Check for autoload bloat wp option list --autoload=on --search='_transient_%'

The transient API’s whole premise — save it temporarily, let it expire on its own — only holds up when get_transient() gets called regularly and WP-Cron’s garbage collection is actually running. Folding a transient audit into the same routine as checking database health with wp db check / wp db optimize is a straightforward way to catch autoload bloat — one of the harder-to-see kinds of database debt — before it accumulates into a real performance problem.