By: Keanan Koppenhaver

In the WordPress ecosystem, transients are a part of the codebase that’s rarely talked about. But depending on what type of code you’re writing in your plugins and themes, transients can be a powerful aid in making your code more performant. If your code is running a complex database query or calling an external API where the results change infrequently, storing the returned data in a transient means that the pages that use this data will load much more quickly for subsequent visitors.

This article will explain what transients are, how they work in WordPress, and how you can start implementing them in your codebase to make your themes and plugins more performant.

What Are Transients and Why Do You Need Them?

The WordPress Transients API is a portion of the WordPress codebase that allows plugin and theme developers to store cached data. This data will eventually expire, after which it’s automatically deleted. Transients are often used to cache data from external API requests that might introduce latency, or from slow-running database queries in which running the query against the actual database on every page load would decrease the site’s performance.

How Do Transients Work Behind the Scenes?

When you save data into a transient, the data is saved into the wp_options table of the WordPress database. At first glance, this makes the Transients API seem similar to the Options API. The main difference is that each transient is given an expiration, after which it will automatically be removed from the database. This is not the case when saving an option in WordPress.

Because transients expire, you need to remember that they won’t always be available in the database. Your code should account for the fact that the transient might be unavailable, and that it might need to make the underlying data call again to repopulate the transient. The expiration is meant as a maximum lifespan, which means that the exact time when a transient vanishes may vary, but it will never exist after the expiration time has passed. Because of this, you always need a fallback in your transient code in case the transient no longer exists when the request for it is made.

When working with transients, there are 3 main functions that you will use: set_transient()get_transient(), and delete_transient(). As with any well-named function, you can infer what these do from their names: setting the value of a transient, accessing a transient, and deleting a transient. They can be used as follows:

// Setting a value
set_transient( $name, $value, $expiration );

// Accessing a value
get_transient( $name );

// Removing a value
delete_transient( $name );

To better understand how transients work in practice, it can help to see a real-world example. Suppose you want to show a quote from an influential historical figure on your blog or site, and you’d like the quote to change daily. You can use an API to help with this, such as Zen Quotes. Their terms of service state that you don’t even need an API key to use the service as long as you provide attribution and are respectful in terms of request volume.

Zen Quotes has an API endpoint specifically for the quote of the day, https://zenquotes.io/api/today. Since the value from this endpoint will only change once a day and you need to be respectful of request volume, it doesn’t make sense to call this endpoint for every visitor to your site. This is a perfect use case for transients.

You can get a quote by calling the API through PHP. The code will resemble the following:

$response = wp_remote_get( 'https://zenquotes.io/api/today );
$body     = json_decode( wp_remote_retrieve_body( $response ) );
$quote = $body->q;

If you use that exact code in a template, it will call the live API every time someone loads a page using that template. This will make an excessive number of requests to the API, and depending on the API’s response time, could also unnecessarily slow down the page load. A better option is using a transient to ensure you’re only calling the live API when you want a new quote:

$response = wp_remote_get( 'https://zenquotes.io/api/today' );
$body     = json_decode( wp_remote_retrieve_body( $response ) );
$quote = $body->q;

set_transient( 'quote', $quote, DAY_IN_SECONDS );

This sets the transient with an expiration of twenty-four hours (using the WordPress DAY_IN_SECONDS constant). There is no check as to whether the transient is already set, though, so the API will still be called on every page load. To check if the transient is set and only call the live API if the transient has expired, you would do something like this:

if( get_transient( 'quote' ) ) {
    $quote = get_transient( 'quote' );
} else {
    $response = wp_remote_get( 'https://zenquotes.io/api/today' );
    $body     = json_decode( wp_remote_retrieve_body( $response ) );
    $quote = $body->q;

    set_transient( 'quote', $quote, DAY_IN_SECONDS );
}

In this example, if the transient is already set, the value of the transient is stored as the quote, and this is the only execution that’s required. If the transient is not available, likely because it has expired, the full API call is made, and the response is stored as a transient for future use.

Similar to set_transient and get_transient, if you want to remove a transient before it’s automatically removed at expiration, you can use the following:

delete_transient( 'quote' );

Using Transients in Your Code

The following is a deeper look at several potential uses for transients.

Caching Long-Running Database Queries

WordPress does a good job of optimizing database queries, especially when you use built-in methods and classes such as WP_Query. However, if you’re querying for a large amount of data from the wp_postmeta table, for example, database queries will be slow enough to affect the performance of your site. This is especially true if you have tens of thousands—or hundreds of thousands—of posts, as some large WordPress sites do.

If the data you’re querying doesn’t change frequently, you could perform this long-running database query once, store the result in a transient, and then perform the lookup from the transient for each subsequent page view. This would ensure that subsequent calls for this data would be performed quickly. If the information does change and you want to update the transient every so often, you could modify the expiration time of the transient so that it expires every half hour, for example. Then, instead of every call for this information performing slowly, only one query every half hour would perform slowly while the transient is repopulated. This would be a huge improvement.

To see what this might look like, consider a scenario where you want to show your site’s readers the five most commonly used words across all posts. You could achieve this a number of ways, such as by registering a shortcode that queries all the posts on the site:

function big_query() {
   $query = new WP_Query(['post_type' => 'post']);

   $words = [];

   if ( $query->have_posts() ) {
       while ( $query->have_posts() ) {
           $query->the_post();
           $content = wp_strip_all_tags(get_the_content());
           $newWords = preg_split('/\W+/', $content, -1, PREG_SPLIT_NO_EMPTY);
           $words = [...$words, ...$newWords];
       }
   } else {
       // no posts found
       return "Publish a post to see the most common words";
   }

   // Restore original post data
   wp_reset_postdata();

   $wordCounts = array_count_values($words);
   arsort($wordCounts);

   $top5 = array_slice(array_keys($wordCounts), 0, 5, true);

   return sprintf("The most common 5 words on the blog are: <b>%s</b>", implode($top5, ', '));

}

add_shortcode('big_query', 'big_query');

This works, and is perfectly responsive when only dealing with a few posts. However, as more posts are added, the calculation could become quite expensive. Because the value will not change arbitrarily, this scenario can benefit from the application of transients. In this scenario, all you would need to do is wrap the bulk of the function in an if statement, save the transient after doing the calculation, and implement a preliminary check to see if the transient is already set:

function big_query() {

   $top5 = get_transient('top_5'); // Check if the value is already stored

   // If not, perform the expensive logic
   if (!$top5) {
       $query = new WP_Query(['post_type' => 'post']);
       $words = [];

       if ( $query->have_posts() ) {
           while ( $query->have_posts() ) {
               $query->the_post();
               $content = wp_strip_all_tags(get_the_content());
               $newWords = preg_split('/\W+/', $content, -1, PREG_SPLIT_NO_EMPTY);
               $words = [...$words, ...$newWords];
           }
       } else {
           // no posts found
           return "Publish a post to see the most common words";
       }

       // Restore original post data
       wp_reset_postdata();

       $wordCounts = array_count_values($words);
       arsort($wordCounts);

       $top5 = array_slice(array_keys($wordCounts), 0, 5, true);
       set_transient('top_5', $top5, WEEK_IN_SECONDS);
   }

   return sprintf("The most common 5 words on the blog are: <b>%s</b>", implode($top5, ', '));

}

One thing to be mindful of in this scenario is that when you add a new post, you would likely want to remove the transient so that these stats can be recalculated. This can be done using the Save Post hook and the previously mentioned delete_transient() function:

function clear_top_5() {
   delete_transient('top_5');
}

add_action('save_post', 'clear_top_5');

Reducing Waiting Time for External API Requests

Another popular use case for transients involves making a call to another system via API. Since you don’t control the external system, you also don’t control how quickly it responds, or whether it’s as consistently available as you would like. With transients, you can get more control over this situation. Just like with the previous example, you can store data returned from the remote API in a transient; on subsequent page loads, you can read the data from the stored transient instead of making another potentially render-blocking remote API call.

Once again, the important aspect to consider here is how frequently the data changes. If you’re calling something like a weather API, where having up-to-the-minute information is important, a transient won’t help much. But if you’re querying for data that changes every few hours, a transient might help you make your site more performant while still ensuring users get the data they need.

Benefits and Drawbacks of Transients

Transients can drastically increase the performance of your code, especially if you frequently use long-running queries or other render-blocking requests that could take a long time to execute. The potential performance improvement is by far the biggest reason to use transients, because they can perform even more quickly if another type of caching is used.

The biggest drawback to using transients is that because of how they’re built, they’re not guaranteed to be available all the way up to the expiration time. If you have data that needs to change over at an exact time, a transient might not be the best choice, because you can’t control this timing.

Conclusion

Transients are a useful concept if you’re building themes or plugins that pull a large amount of data from the database or integrate with outside data sources, especially if your code is going to be running on a high-traffic site. You can use transients to cache particularly slow operations so that site performance stays high when data doesn’t need to be refreshed.

As your code gets more complex, transients can make sure you’re getting the best performance you can out of your codebase. This will help you guarantee a better experience for your users.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s