The WordPress Transients API is a built-in caching system that stores expensive query results with an expiration time. If your site runs slow database queries, calls external APIs, or computes data that does not change every page load, transients can cut seconds off your response time without installing a caching plugin. The API has been part of WordPress since version 2.8 and works with three functions: set_transient(), get_transient(), and delete_transient(). This tutorial covers when to use them, when not to, and the production patterns that separate a clean implementation from one that creates more problems than it solves. If you are new to WordPress development, our custom post types guide covers another core API that pairs well with transients for structured content.
Key Takeaways
- Three functions handle everything: set_transient() stores data with an expiration, get_transient() retrieves it, delete_transient() clears it. The API handles serialization automatically
- Expiration is a maximum, not a guarantee: transients can disappear before their expiration time. Always code a fallback that regenerates the data if the transient returns false
- Use strict comparison (===) when checking returns: get_transient() returns false when data is missing, which collides with any cached value that evaluates to false. Use false === get_transient() to avoid bugs
- Object cache backends make transients faster: managed hosts like Kinsta, Cloudways, and ScalaHosting include Redis or Memcached, which moves transients from the database to memory (10-100x faster)
- Do not cache user-specific data with unique keys: one transient per user fills your database or evicts useful cache entries. Transients work for shared, regeneratable data
What Are WordPress Transients?
Transients are temporary key-value pairs stored in WordPress with an expiration time. They work like the Options API (get_option / update_option) but with one critical difference: transients expire. After the expiration time passes, WordPress deletes the data and your code regenerates it on the next request.
By default, transients live in the wp_options table. When a persistent object cache backend is installed (Redis, Memcached), transients automatically move to memory instead of the database. This happens without any code changes on your end. The same set_transient() call that writes to the database on a basic host writes to Redis on a managed host. The API abstracts the storage layer entirely.
WordPress provides time constants for readable expiration values: HOUR_IN_SECONDS (3,600), DAY_IN_SECONDS (86,400), WEEK_IN_SECONDS (604,800). Use these instead of raw numbers.
The Three Core Functions
set_transient()
Stores a value with a name and expiration time. Returns true on success.
set_transient( 'my_cached_data', $data, DAY_IN_SECONDS );
Parameters: the transient name (string, max 172 characters), the value (any type, automatically serialized), and the expiration in seconds. If you omit the expiration or set it to 0, the transient never expires automatically but can still be deleted by object cache eviction or manual deletion.
get_transient()
Retrieves a stored transient by name. Returns the cached value on success, or false if the transient has expired, been deleted, or never existed.
$data = get_transient( 'my_cached_data' );
if ( false === $data ) {
// Cache miss — regenerate the data
$data = expensive_database_query();
set_transient( 'my_cached_data', $data, DAY_IN_SECONDS );
}
return $data;
The false === $data check uses strict comparison deliberately. If your cached value is 0, an empty string, or an empty array, loose comparison (== false) would incorrectly treat it as a cache miss. Always use === with transient returns.
delete_transient()
Removes a transient immediately. Returns true on success.
delete_transient( 'my_cached_data' );
Use this when the underlying data changes and the cache needs to refresh before its natural expiration. For example, delete a cached post count whenever a post is published by hooking into save_post.
When to Use Transients
Transients are the right tool when all three conditions are true: the data is expensive to generate, the data does not change on every request, and the data can be safely regenerated if the cache disappears.
Expensive Database Queries
A WP_Query that joins multiple tables, filters across custom taxonomies, or aggregates data from thousands of posts can take hundreds of milliseconds. Cache the result for an hour and every subsequent page load skips the query entirely.
External API Responses
Calls to third-party APIs (weather data, social media counts, payment gateway status) add latency and are subject to rate limits. Cache the response for a reasonable duration and serve the cached version until it expires.
Computed or Aggregated Data
Counts, averages, formatted output, or any data that requires processing multiple database rows into a single result. Calculate once, cache the result, and serve the cached version until the underlying data changes.
When Not to Use Transients
User-specific data with unique keys. Creating one transient per user (like user_123_dashboard_data) fills the wp_options table with thousands of rows. With Memcached, it evicts useful cache entries. Transients work for shared data, not per-user sessions.
Data that must never disappear. Transients can vanish at any time. An object cache restart, a database cleanup, or a hosting migration can wipe them. If losing the data breaks functionality, use the Options API instead.
Simple queries that are already fast. If your query takes 5ms, adding transient logic makes the code more complex without meaningful speed improvement. The overhead of checking, storing, and invalidating the cache can exceed the cost of the query itself. Only cache what is genuinely slow.
WordPress Transients API: Production Patterns with Code
Pattern 1: Cache a Slow WP_Query
function get_featured_products() {
$cached = get_transient( 'featured_products' );
if ( false !== $cached ) {
return $cached;
}
$query = new WP_Query( [
'post_type' => 'product',
'posts_per_page' => 12,
'meta_key' => '_featured',
'meta_value' => 'yes',
'orderby' => 'meta_value_num',
'meta_key' => '_price',
'order' => 'ASC',
] );
$products = $query->posts;
set_transient( 'featured_products', $products, HOUR_IN_SECONDS );
return $products;
}
Invalidate when a product is updated:
add_action( 'save_post_product', function() {
delete_transient( 'featured_products' );
} );
Pattern 2: Cache an External API Call
function get_github_stars( $repo ) {
$key = 'github_stars_' . sanitize_key( $repo );
$cached = get_transient( $key );
if ( false !== $cached ) {
return $cached;
}
$response = wp_remote_get( "https://api.github.com/repos/{$repo}" );
if ( is_wp_error( $response ) ) {
return 0; // Don't cache errors
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
$stars = (int) ( $body['stargazers_count'] ?? 0 );
set_transient( $key, $stars, 6 * HOUR_IN_SECONDS );
return $stars;
}
Notice: errors are not cached. If the API is down, the next request retries instead of serving a cached failure for six hours.
Pattern 3: Cache Computed Aggregation
function get_site_stats() {
$cached = get_transient( 'site_stats' );
if ( false !== $cached ) {
return $cached;
}
global $wpdb;
$stats = [
'total_posts' => (int) $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'post'"
),
'total_comments' => (int) $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_approved = '1'"
),
'total_users' => (int) $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->users}"
),
];
set_transient( 'site_stats', $stats, DAY_IN_SECONDS );
return $stats;
}
Transients and Object Cache Backends
On a default WordPress install, transients store in the wp_options table. This means every get_transient() call hits the database. It is faster than re-running the original query, but still involves a database round-trip.
When a persistent object cache backend is installed (Redis or Memcached), WordPress stores transients in memory instead of the database. Memory reads are 10-100x faster than database reads. The code does not change at all. The same set_transient() and get_transient() calls work identically. WordPress detects the object cache and redirects storage automatically.
This is why managed WordPress hosts that include Redis or Memcached get more value from transients than shared hosts running on the default database backend. If your site relies heavily on transients and you are on shared hosting, the performance gains are real but limited. Moving to a host with object caching unlocks the full potential.
Multisite Considerations
For WordPress Multisite installations, use set_site_transient(), get_site_transient(), and delete_site_transient(). These store data network-wide instead of per-site. The behavior is identical to the standard functions but the data is shared across all sites in the network. If you are managing multiple WordPress sites on one server, our multi-site hosting guide covers the infrastructure side.
Common Mistakes
Not handling cache misses. Every get_transient() call must have fallback logic. Transients can disappear at any time. If your code assumes the data exists, it will break on cache eviction, server restart, or database migration.
Using loose comparison. if ( ! $data ) treats 0, empty arrays, and empty strings as cache misses. Use if ( false === $data ) instead.
Caching errors. If an API call fails, do not cache the error response. The next request should retry, not serve a cached failure for the full expiration period. Check for errors before calling set_transient().
Never-expiring transients without cleanup. Setting expiration to 0 creates a transient that never auto-expires and gets autoloaded on every page request. If you store large data this way, it loads into memory on every single page load across the entire site. Always set a reasonable expiration unless you have a specific reason not to.
Not invalidating on data changes. If you cache a post count and a new post is published, the cached count is stale. Hook into the relevant actions (save_post, edit_term, wp_update_comment_count) to delete the transient when the underlying data changes. For more on WordPress hooks and when they fire, our deployment guide covers the action lifecycle in the context of CI/CD.
Hosting That Makes Transients Faster
Transients on the default database backend are better than no caching. Transients on Redis or Memcached are an order of magnitude faster. The hosting provider determines which backend your transients use.
Kinsta includes Redis object caching on all plans. Every transient call goes to memory instead of the database, and their server-side caching stack handles invalidation automatically. It is the most hands-off option for developers who want transients to work at full speed without configuring anything. For a deeper look at what Kinsta offers developers, see our Cloudways vs Kinsta comparison.
Cloudways gives you Redis and Memcached as installable add-ons across multiple cloud providers (DigitalOcean, Vultr, AWS, Google Cloud). You pick the backend and the cloud infrastructure independently. More configuration than Kinsta but more flexibility in server sizing and provider choice.
ScalaHosting offers managed VPS plans with their own SPanel control panel. You get root access to install Redis or Memcached yourself, plus the infrastructure control that shared hosting locks you out of. It is the best option if you want object caching AND full server access without managing raw configs. For a detailed review, see our ScalaHosting review.
Frequently Asked Questions
What is the difference between transients and the Options API?
Both store key-value pairs in the wp_options table. Transients have an expiration time and can disappear at any moment. Options persist indefinitely until explicitly deleted. Use transients for temporary cached data. Use options for permanent settings.
Do transients work with Redis and Memcached?
Yes. When a persistent object cache backend is installed, WordPress automatically stores transients in memory instead of the database. No code changes needed. The same set_transient() and get_transient() calls work identically regardless of backend.
How long should I set transient expiration?
It depends on how often the underlying data changes. External API calls: 1-6 hours. Database aggregations: 1-24 hours. Data that changes on user action (post counts, term counts): use event-based invalidation with delete_transient() on the relevant hook instead of relying on time expiration.
Can transients disappear before they expire?
Yes. Expiration is a maximum, not a guarantee. Object cache restarts, database cleanups, hosting migrations, and memory pressure on Memcached/Redis can all evict transients early. Always include fallback logic that regenerates the data on a cache miss.
Should I use transients or a caching plugin?
Both. They solve different problems. Transients cache specific data (query results, API responses) at the code level. Caching plugins (WP Super Cache, WP Rocket) cache entire HTML pages at the server level. Transients speed up the PHP execution. Page caching skips PHP entirely. Use transients inside your plugin or theme code, and a page cache for the overall site.
Summary
The Transients API is the simplest performance tool in WordPress that most developers underuse. Cache expensive queries, API calls, and computed data with set_transient(). Retrieve with get_transient() using strict comparison. Invalidate with delete_transient() when the underlying data changes. Move to a host with Redis or Memcached (Kinsta, Cloudways, or ScalaHosting) to get 10-100x faster reads. Always assume transients can disappear and code your fallbacks accordingly. For more WordPress performance techniques, our headless WordPress guide covers when to decouple the frontend entirely for maximum speed.