WordPress transients are a built-in caching layer for storing data with an automatic expiration time. Set a transient with a key, a value, and a number of seconds; WordPress remembers the value until that time runs out, then forgets it. Three functions cover almost everything: set_transient(), get_transient(), and delete_transient().
Transients are the right tool when you have data that’s expensive to compute (a slow database query, an external API call, a complex calculation) but doesn’t need to be perfectly fresh. Cache it for 5 minutes, an hour, a day, however long is acceptable, and serve the cached version on every subsequent request until it expires.
Personally, I love transients because they make it trivial to cache external API requests on sites that pull data from third-party services. Set the response under a key with a sensible expiration and you stop hammering the upstream API on every page load. Your site stays fast, the third-party service is happy, and you don’t pay rate-limit tax on traffic spikes.
This guide covers what transients are, the three functions, where they’re actually stored, site transients for multisite, a real example caching an API response, and the pitfalls that bite people (key length limits, race conditions, transients that never expire).
What Are WordPress Transients?
A transient is a named piece of data with an expiration date. You store a value under a string key, set how long WordPress should keep it, and any code can fetch it back by that same key until the timer runs out.
Behind the scenes, WordPress stores transients in the wp_options table by default, using two rows per transient: one for the value and one for the expiration timestamp.
When you ask for a transient, WordPress checks the expiration row first. If it’s past, WordPress deletes both rows and returns false. If it’s still valid, WordPress returns the stored value. (If you’ve installed an object cache like Redis or Memcached, the storage location changes, more on that below.)
The mental model: transients are server-side caching as a key-value store, with a TTL on every value. If you’ve used Redis, Memcached, or any “give me cache.get(key) / cache.set(key, value, ttl)” API in another framework, transients are the WordPress equivalent.
When to Use Transients
Reach for transients when you have data that:
- Is expensive to compute. A slow
WP_Queryacross thousands of posts, a complex aggregation, anything that takes more than a few hundred milliseconds. - Comes from an external API. Weather, exchange rates, social media counts, a sister-site’s RSS feed. Cache the response for 5-60 minutes so you’re not hammering the API on every page load (and your site stays fast even when the API is slow or down).
- Tolerates being slightly stale. If “five minutes old” or “an hour old” is fine, transients work. If the data must be real-time accurate to the millisecond, transients aren’t the right layer.
- Doesn’t need to survive forever. Transients can disappear earlier than expected (an object cache restart, a manual flush). Don’t store anything you can’t recompute.
Don’t use transients for: user-specific data (a transient is global to the site, not per-user), data that must persist permanently (use the options table or post meta instead), or anything where staleness leads to security issues (auth tokens, session data, anti-CSRF tokens).
The Three Core Functions
Almost every transient interaction comes down to three functions. Drop this code into your theme’s functions.php or a custom plugin.
set_transient( $key, $value, $expiration )
set_transient( 'smartwp_popular_posts', $posts_array, HOUR_IN_SECONDS );
Three arguments: the key (a string, max 172 characters), the value (any serializable PHP value: string, array, object, scalar), and the expiration in seconds. WordPress defines convenience constants you should use instead of raw numbers:
MINUTE_IN_SECONDS(60)HOUR_IN_SECONDS(3600)DAY_IN_SECONDS(86400)WEEK_IN_SECONDS(604800)MONTH_IN_SECONDS(2592000)YEAR_IN_SECONDS(31536000)
So 12 * HOUR_IN_SECONDS reads as “12 hours” without needing a calculator. set_transient() returns true on success, false on failure (usually because the key is too long or the value can’t be serialized).
get_transient( $key )
$posts = get_transient( 'smartwp_popular_posts' );
Returns the stored value if it exists and hasn’t expired, or false if it doesn’t exist or has expired. The standard pattern is “try the cache first, recompute and store if missing”:
$posts = get_transient( 'smartwp_popular_posts' );
if ( false === $posts ) {
// Cache miss: do the expensive thing
$posts = run_expensive_query();
set_transient( 'smartwp_popular_posts', $posts, HOUR_IN_SECONDS );
}
// Use $posts whether it came from cache or was just computed
Use strict comparison (=== or !==) against false, because your actual cached value might be an empty array, an empty string, or zero, all of which would pass a loose check.
delete_transient( $key )
delete_transient( 'smartwp_popular_posts' );
Removes the transient immediately. Use this when something invalidates the cached value before the expiration runs out. For example, if you cache popular posts for an hour but a new post just got published with a million views, you want to clear the cache so the next reader sees the updated list:
add_action( 'save_post', function( $post_id ) {
delete_transient( 'smartwp_popular_posts' );
} );
Site Transients (for Multisite)
On WordPress Multisite networks, regular transients are scoped to a single site. If you need a value shared across every site in the network, use the site-transient variants:
set_site_transient( $key, $value, $expiration )get_site_transient( $key )delete_site_transient( $key )
The API is identical to the regular functions; only the storage scope changes. On single-site WordPress installs, site transients behave exactly like regular transients, so it’s safe to use them either way if you’re not sure whether your code will run on multisite.
Where Transients Are Actually Stored
The storage location matters because it affects performance and persistence. Two cases:
- Without a persistent object cache: transients live in
wp_optionsas two rows per transient (_transient_{key}for the value,_transient_timeout_{key}for the expiration). Reading and writing both hit MySQL. Site transients use thewp_sitemetatable on multisite (with a_site_transient_prefix). - With a persistent object cache (Redis, Memcached): transients are written to the object cache instead of the database, where reads are an order of magnitude faster. The expiration handling moves to the cache layer too, so the cache evicts expired entries automatically and the
wp_optionstable stays clean. Object Cache Pro, Redis Object Cache, and W3 Total Cache all enable this.
The practical implication: on a site without an object cache, every transient you set adds two rows to wp_options. Set a transient with a 60-second expiration on every page load and you generate massive database churn.
With an object cache, the same code is roughly free. If you’re writing transient-heavy code, an object cache is a meaningful performance upgrade.
A Real Example: Caching an External API Response
Say you want to display GitHub stars for a project on your sidebar without hitting the GitHub API on every page load. Transients make this clean:
function smartwp_get_github_stars( $repo ) {
$cache_key = 'smartwp_github_stars_' . md5( $repo );
$stars = get_transient( $cache_key );
if ( false !== $stars ) {
return $stars;
}
$response = wp_remote_get( "https://api.github.com/repos/{$repo}" );
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
// API failed: cache the failure briefly so we don't hammer it
set_transient( $cache_key, 0, 5 * MINUTE_IN_SECONDS );
return 0;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
$stars = isset( $data['stargazers_count'] ) ? (int) $data['stargazers_count'] : 0;
set_transient( $cache_key, $stars, HOUR_IN_SECONDS );
return $stars;
}
// Usage in a template:
echo smartwp_get_github_stars( 'WordPress/gutenberg' );
A few details worth noting:
md5( $repo )prevents the repo name from blowing past the 172-character key limit and avoids slashes or spaces in the key.- The failure case still sets a short-lived transient (5 minutes). This prevents what’s called a “thundering herd” where every page load retries a failing API simultaneously.
- The success case caches for an hour. Tune that number to your tolerance for staleness.
Common Pitfalls
- Key length limit. Transient keys are limited to 172 characters on the option-table storage (the column type imposes the cap). Site transient keys cap at 167 characters. If your key exceeds the limit,
set_transient()silently fails. Best practice: keep keys short, usemd5()orhash()to fold long inputs. - Race conditions. Two requests arriving at the same time both find the cache empty, both run the expensive query, both write the transient. WordPress doesn’t lock on transient writes. For most use cases this is harmless (two writes overwrite each other, no big deal). For expensive operations you want to dedupe, wrap the work in a lock pattern using a second short transient as the lock.
- Transients that “never expire.” Passing
0as the expiration creates a transient that never expires on its own. This sounds convenient but it has a hidden problem on the option-table storage: WordPress treats no-expiry transients as autoloaded options, which means they load on every page request. Set a long but finite expiration (a day, a week) instead of 0. - Storing too much data. Transients serialize the value before storing. A 50MB array is theoretically possible but ruins the option table. Cache small, focused values (an integer, a small array of post IDs, an API response). Don’t cache entire query result sets that include full post content.
- Object cache flush surprises. When an object cache is enabled, transients live in the cache, not the database. A cache flush (server restart, manual
wp cache flush) wipes every transient instantly. Always assume transients can disappear; never store anything you can’t regenerate.
Cleaning Up Expired Transients
WordPress doesn’t aggressively delete expired transients from the database. The expiration row just sits there until the transient is requested again (at which point the lookup returns false and WordPress cleans both rows up). On busy sites, expired transients can accumulate in wp_options over time, slowly bloating the table.
Three ways to clean them up:
- WordPress runs its own cleanup. Since WordPress 4.9, core schedules a
delete_expired_transientstask via WP-Cron that runs daily. Most sites don’t need to think about it. - Trigger cleanup manually: call
delete_expired_transients()from your own code if you want to force a sweep. - From WP-CLI:
wp transient delete --expiredwipes expired transients, orwp transient delete --allwipes every transient on the site (useful when debugging). Thewp transient listcommand shows what’s currently stored.
Frequently Asked Questions
What’s the difference between transients and the Options API?
Options are permanent (you have to delete them explicitly); transients have an expiration timestamp and WordPress treats them as cache. Internally, transients on a database-backed store use the same wp_options table as regular options, but they’re prefixed (_transient_ and _transient_timeout_) and core handles the expiration logic for you. Use options for site configuration; use transients for cached values that you can always recompute.
Are WordPress transients the same as caching plugins?
No. Caching plugins (WP Rocket, LiteSpeed Cache, W3 Total Cache) generate static HTML files for entire pages and serve them to anonymous visitors. Transients are a smaller, programmer-facing cache for specific values inside your PHP code. They complement each other: a page cache speeds up the rendered output, transients speed up the expensive PHP that builds the output in the first place.
What is the maximum length of a WordPress transient key?
172 characters for regular transients, 167 characters for site transients. The limits come from the underlying database column. If your key exceeds the limit, set_transient() silently returns false. The safe approach is to keep keys short and hash any long-input components with md5() or hash( 'sha256', $input ).
Can I store objects in a transient?
Yes. Transients accept any serializable PHP value: strings, integers, arrays, objects, booleans. WordPress runs serialize() on the value when storing and unserialize() when fetching, so you get back the same structure you put in. The exception is resources (database connections, file handles, stream resources), which can’t be serialized; storing one will fail or return corrupted data when fetched.
What happens if I set an expiration of 0?
The transient never expires on its own, and WordPress treats it as a regular autoloaded option (which means it loads on every page request, adding to memory overhead). It also won’t be cleaned up by the scheduled delete_expired_transients task. In practice, you should pick a finite expiration even when you want “long-lived” caching: a week (WEEK_IN_SECONDS) or a month gives you the same behavior without the autoload tax.
How do I see all transients on my site?
From WP-CLI: wp transient list shows every transient with its key, value, and remaining lifetime. From the database: query SELECT option_name, option_value FROM wp_options WHERE option_name LIKE '\_transient\_%'. From the admin: install a debugging plugin like Query Monitor or Transients Manager, which surface the list in a UI.
Should I use transients or the WordPress Object Cache API directly?
For most cases, transients. They have a simpler API, automatic database fallback (your code keeps working if the object cache isn’t installed), and built-in expiration handling. The lower-level wp_cache_* functions are more granular (cache groups, non-persistent caching) but require an object cache to actually persist between requests. If you’re writing a plugin that needs to work everywhere, transients are the safer default.
Do transients work with the REST API?
Yes, on the server side. You can use transients inside REST API endpoint callbacks to cache expensive lookups before returning the response. The REST API itself doesn’t expose transient endpoints (and shouldn’t, since transients aren’t meant to be client-readable), but using transients to speed up your REST endpoints is one of the most useful applications of the API.
Wrapping Up
Transients are one of those quietly powerful WordPress features that most developers don’t reach for often enough. Three functions, a key-value-with-expiration model, and you get a clean caching layer for expensive queries, slow API calls, and any computation you’d rather not redo on every request.
The short version: use transients for data that’s expensive to compute and tolerates being slightly stale, keep your keys short, set a finite expiration (never 0), invalidate on relevant events (save_post, settings updates, etc.), and consider an object cache if you’re writing transient-heavy code on a busy site.
For more dev-side WordPress utilities: functions.php covers where this kind of code typically lives, WP-Cron explains the scheduled cleanup of expired transients, WP-CLI commands covers the wp transient family, and the REST API guide covers where transients fit in headless and API-driven builds. The canonical reference is Transients on developer.wordpress.org.


