Cache WordPress Query Results and API Responses with the Transient API

The WordPress Transients API is a key-value cache built on top of the options table that stores time-limited data, making it the simplest way to cache expensive database queries, remote API responses, or computed HTML fragments without installing a caching plugin. Transients work identically whether your site uses the default file-based object cache or a persistent backend like Redis or Memcached — the API functions are the same, and the performance difference is enormous in favour of a persistent store. When a persistent object cache is active, set_transient() stores the value in memory rather than writing a row to wp_options, eliminating the database write entirely and making reads sub-millisecond. A transient miss — when the key is expired or was never set — triggers a fresh data fetch and re-caches the result, so the pattern is always: check the transient, fall back to the expensive operation on a miss, and re-set the transient. Choosing an expiration time requires balancing freshness against cache efficiency — an hourly expiry works well for navigational widgets like recent posts or popular tags, while a daily expiry suits slowly changing data like post counts per category. Transient keys are limited to 172 characters; longer keys should be hashed with md5() to stay within the limit while remaining deterministic. Versioning the cache key — appending a version string from get_option() — lets you invalidate all related transients by incrementing the version instead of manually deleting every key. The custom database table guide shows when a dedicated table is a better long-term solution than transients for high-volume data. You can inspect active transients and their remaining TTL with the Transient Manager plugin or by querying wp_options for rows with keys prefixed _transient_timeout_. The JS and CSS deferral post covers the front-end half of the same performance goal — reducing payload and blocking time alongside the database query reduction that transients provide. Always delete a transient with delete_transient() immediately after saving a post or updating an option to prevent stale data from persisting until the TTL expires.

Problem: WordPress widgets and shortcodes that run heavy WP_Query calls or fetch remote API data on every page load slow down the site and waste server resources on identical requests.

Solution: Wrap expensive data-fetching operations in get_transient() / set_transient() calls and use a versioned cache key so related transients can be bulk-invalidated on content updates.

// Cached navigation widget: recent posts per category
function get_cached_category_posts(int $cat_id, int $limit = 5): array {
    $version   = (int) get_option('my_cache_version', 1);
    $cache_key = "cat_posts_{$cat_id}_{$limit}_v{$version}";

    $posts = get_transient($cache_key);

    if (false === $posts) {
        $posts = get_posts([
            'cat'            => $cat_id,
            'numberposts'    => $limit,
            'post_status'    => 'publish',
            'no_found_rows'  => true,
            'orderby'        => 'date',
            'order'          => 'DESC',
        ]);
        set_transient($cache_key, $posts, HOUR_IN_SECONDS);
    }

    return $posts ?: [];
}

// Invalidate all versioned transients by bumping the version
function invalidate_category_post_cache(): void {
    $version = (int) get_option('my_cache_version', 1);
    update_option('my_cache_version', $version + 1, false);
}
add_action('save_post_post',    'invalidate_category_post_cache');
add_action('deleted_post',      'invalidate_category_post_cache');
add_action('transition_post_status', function($new, $old) {
    if ($new !== $old) invalidate_category_post_cache();
}, 10, 2);

NOTE: Install Redis Object Cache (plugin) and configure WP_REDIS_HOST in wp-config.php to move transient storage from the database to memory — this turns each transient read from a MySQL query into a sub-millisecond memory lookup.