Database queries are the most common performance bottleneck on WordPress sites, and the problem compounds as content volume grows. A custom WP_Query that aggregates post counts across multiple taxonomies, a $wpdb query that joins several tables to build a statistics dashboard, a get_posts() call with complex meta conditions, or an external API request that fetches current exchange rates or weather data — each of these can take hundreds of milliseconds to complete and needs to be repeated on every page load that requires the result. For data that does not change on every request, repeating an expensive operation on each load is pure waste. The WordPress Transients API provides exactly the right tool for this: a thin caching layer that stores arbitrary PHP values in the database (or in an object cache like Redis or Memcached if one is configured) with an automatic expiration time. The pattern is the standard cache-aside pattern: before executing the expensive operation, check whether a valid cached version exists and return it if so; if not, run the operation, store the result with a defined TTL, and return the fresh value. Three functions cover the entire API: set_transient( $key, $value, $expiration ) writes a cached value with an expiration in seconds, get_transient( $key ) reads it back (returning false if expired or absent), and delete_transient( $key ) removes it immediately when you need to invalidate the cache after a data change. The expiration argument accepts WordPress time constants like HOUR_IN_SECONDS, DAY_IN_SECONDS, and WEEK_IN_SECONDS for readability. When a persistent object cache is active, transients are stored there instead of the database, making reads near-instantaneous and eliminating even the lightweight database lookup. Without a persistent cache, transients live in wp_options and are cleaned up by the scheduled database maintenance covered in our posts on optimizing database tables and scheduling recurring cleanup tasks. The cache key should be unique and descriptive — if it varies by context (user role, current page, or query parameters) include those variables in the key to prevent stale cross-context cache hits. A good cache key convention includes a plugin or theme prefix, a data type identifier, and any context variables: ha_featured_posts_cat_5 or ha_api_weather_kyiv.
Problem: An expensive database query or external API call runs on every page load, causing slow response times that grow worse as traffic increases.
Solution: Wrap the expensive operation in a transient cache. Add to your theme or plugin code:
<?php
function ha_get_featured_posts() {
$cache_key = 'ha_featured_posts_v1';
// Try to get cached data first
$posts = get_transient( $cache_key );
if ( false === $posts ) {
// Cache miss — run the expensive query
$posts = get_posts( array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 6,
'meta_key' => 'featured',
'meta_value' => '1',
'orderby' => 'date',
'order' => 'DESC',
) );
// Store result for 12 hours
set_transient( $cache_key, $posts, 12 * HOUR_IN_SECONDS );
}
return $posts;
}
// Invalidate the cache when any post is saved or updated
add_action( 'save_post', 'ha_clear_featured_posts_cache' );
function ha_clear_featured_posts_cache( $post_id ) {
// Ignore autosaves and revisions
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
return;
}
delete_transient( 'ha_featured_posts_v1' );
}
// Usage example
$featured = ha_get_featured_posts();
foreach ( $featured as $post ) {
setup_postdata( $post );
the_title( '<h2>', '</h2>' );
}
wp_reset_postdata();
NOTE: Always invalidate transient caches when the underlying data changes — in the example above, save_post clears the cache so the next page load fetches fresh data. Without invalidation, editors may publish new featured posts and see the old cached list for up to 12 hours. If you use a persistent object cache (Redis, Memcached), prefer wp_cache_set() and wp_cache_get() over transients for in-memory storage without database overhead. For site-wide data that does not vary by user, always use a single consistent key; for user-specific caches, append the user ID to ensure each user gets their own cache slot.