How to store temporary cached data with WordPress transients

Transients are such a powerful part of WordPress, yet are overlooked and under utilized. Using the transients API, you can store values in a standardized way, giving them an expiration time. This frees up space in the database and can lead to significant load time improvements.

In this article, I’ll show you everything you need to know to use transients, and a few examples to show you just how powerful they can be!

Why use expiring data?

The first question that might be on your mind is, “Why would we use a bit of data that may suddenly disappear from the database?” This is a completely valid question, but misses a very important point. We don’t just use transients to clean up our database. We use them to display saved (cached) complex data, instead of retrieving them each time a page loads.

Let’s assume that you display RSS feeds in your sidebar. If you don’t use any caching mechanism, those feeds are queries live from the sites in question — this can take a while. A far better way to go about business is to store the feed’s state in a transient which expires every hour.

When the hour is up, our website doesn’t just stop showing RSS feeds. It detects that the transient that stored the feed no longer exists, queries the appropriate sites, and saves the data as the same transient.

The bottom line is that instead of loading feed results on each and every page load, we can get away with it once every hour. Feeds can take a while to populate, so this leads to significant server load reductions on the back end, and load time reductions on the user-facing side.

Using the transients API

There are only three basic operations you’ll need to know: setting, getting, and deleting. To set a transient, you need to specify a name, a value, and an expiration time. To get or delete a transient, you just need its name. Let’s take a look at this in more detail.

Setting transients

To set a transient we use the set_transient() function which takes three parameters: the name, value and expiration time which is expressed in seconds:

set_transient( 'current_weather', 'pleasant', 86400 );

The code above will set a transient named current_weather with the value of pleasant. It has an expiration of 86,400 seconds, which is a day. Once the day has passed, the transient will not exist.

Getting transients

You’re actually over the hump, as setting transients is the most difficult step. Getting them is a matter of knowing their name. That’s all you need to pass to the get_transient() function:

<?php
$weather = get_transient( 'current_weather' );
?>
<p>The weather today is <?php echo $weather ?></p>

Deleting transients

Deleting transients is similarly simple to getting them. You simply pass the name to the delete_transient() function and it is removed from the database.

delete_transient( 'current_weather' );

Why do this when they expire on their own? You’ll see in some of our examples below that, in a few scenarios, you want to set very long expiration times, but refresh the transient before then due to some user action.

Multisite transients

Now that I’ve lulled you into a false sense of safety, I’ll drop three more functions on you. Not to worry though, they are just as simple as the ones above. Each function we’ve discussed so far has a variant for storing a site-wide transient.

If you’re working in a multisite installation, transients manipulated using the get/set/delete_transient functions only work with the particular site in question. If you want to set a get/set/delete a transient which is available across the network, you can use get_site_transient(), set_site_transient(), and delete_site_transient().

set_site_transient( 'current_weather', 'perfect', 86400 );
get_site_transient( 'current_weather' );
delete_site_transient( 'current_weather' );

Specifying expiration

There are three value types available to you when specifying expiration:

  1. You can create a never-expiring transient by using 0 as the timeout value.
  2. You can set a fixed expiration time using a numeric value in seconds
  3. You can set a virtually never-expiring transient using a huge number

Since we’ve been discussing how great timeout values are, it may be confusing to see that you can make never-expiring transients. These are useful if you want to use transients as a de facto cache. You could store related posts as a never-expiring transient. The only time you do update this is if a new post is published. In this case, it is manually deleted and re-created.

The fact that you can create immortal transients in two ways seems even more weird, but there’s a reason behind it. If you use “0”, the transient will be auto-loaded. This means that WordPress will load its value from the database on every page load. When you get its value, WordPress is actually looking it up in a retrieved array, not in the database directly. This is great for things you intend to use on every page load

If you set a far-in-the-future expiration date — say 3,153,600,000 (100 years) — the transient is not auto-loaded. When you get its value, it is pulled straight from the database. This is desirable if you only need to use it on an obscure page on your site.

Finally, to make our life easier, WordPress ships with 5 constants which allow us to specify expirations easily:

    MINUTE_IN_SECONDS
    HOUR_IN_SECONDS
    DAY_IN_SECONDS
    WEEK_IN_SECONDS
    YEAR_IN_SECONDS
set_transient( 'resolution', 'I will drink more water this year', YEAR_IN_SECONDS );

Practical applications

So, what practical applications are we looking at here? In theory you should take a look at all the aspects of your site which require database interactions on each load, but don’t really change much over time. One good example could be your website’s footer.

Caching your entire footer

A typical footer will consist of a logo, a menu, and perhaps some copyright text. The menu is usually dynamic. You might modify it from the menu manager in the backend. In many cases this is a data-intensive operation, as post objects will be loaded for each menu item. That being said, how many times do you really change your footer menu? If the answer is “not too many”, you can consider using a transient. Let’s see how a typical footer looks in the footer.php file:

<footer id='site-footer'>
  <div id='footer-logo'>My Website Name</div>
  <nav>
    <?php wp_nav_menu( array( 'location' => 'footer_menu' ) ) ?>
  </nav>
  <div id='credits'>&copy; <?php echo date('y') ?> My Website</div>
</footer>

Ideally we want to store the output of this whole code block as a transient. If the transient exists, we use the value stored within. If it doesn’t, we use the code above to create the transient. Let’s hide all that behind a function named wdd_footer(). Using this, you can replace the whole footer block with the following:

<?php wdd_footer() ?>

In the functions.php file, you can define the content of this function. Here’s the full code with an explanation:

function wdd_footer() {
  $footer = get_transient( 'wdd_footer' );
  if ( empty( $footer ) ) {
    ob_start();
    ?>
    <footer id='site-footer'>
      <div id='footer-logo'>My Website Name</div>
      <nav>
        <?php wp_nav_menu( array( 'location' => 'footer_menu' ) ) ?>
      </nav>
      <div id='credits'>&copy; <?php echo date('y') ?> My Website</div>
    </footer>
    <?php 
    $footer = ob_get_clean();
    set_transient( 'wdd_footer', $footer, 0 );
  }
  echo $footer;
}

The first thing we do is get the transient. If it exists, discard the whole if statement and echo it. Job well done. If the transient does not exist, we need to assemble our footer. I used an output buffer to store echoed data in a variable. This is not the most elegant solution; but it can easily be rewritten to return data instead of echoing it.

Finally, the transient is set with a 0 expiration. I set it to 0 instead of using a far-future expiration because I want to autoload the value, the footer is used on every page.

So far so good, right now your website should look the same as it did before the modification. There is, however, an issue when you modify your footer. The changes will not be visible. Your footer has been stored as a transient that never expires, this is to be expected. The trick is to force-reset the transient when the menu is changed.

Luckily this is easy with the wp_update_nav_menu hook which fires whenever a menu is modified.

add_action( 'wp_update_nav_menu', 'wdd_update_footer' );
function wdd_update_footer() {
  delete_transient( 'wdd_footer' );
}

Note how easy this is, we don’t even need to delete and re-build our footer; deleting the transient is enough. When the site loads next, the wdd_footer() function will detect that the transient doesn’t exist and will re-create it for us.

Caching data with expiration

In the previous example, we used the expiration date to prevent the transient from ever refreshing on its own. Since the footer is only modified due to direct user action, it makes sense to do this. What if you want to display the number of views a specific post author has received on his posts in author boxes? Is complete accuracy down to the second important here?

Let’s assume that you have a bit of code in place which increments the view counter whenever someone visits a particular post. Each post has its own view counter, the view count is stored as post meta using the post_view_count field. Whenever the view count for an author is shown, these post metas are added together. Let’s create a function for this:

function author_post_count( $author_id ) {
  global $wpdb;
  
  $post_count = get_var( "SELECT SUM( meta_value ) FROM $wpdb->postmeta WHERE meta_key = 'post_view_count' AND post_id IN (SELECT ID  $wpdb->posts WHERE post_author = $author_id AND post_type = 'post' AND post_status = 'publish' ) " );
  
  return $post_count;
}

The query above will run each and every time the author’s post count is shown, unnecessarily. Why not update the number twice every day? Don’t forget, if you have 1,000 page views a day you are saving as much as 998 queries! Let’s rewrite our function to use transients:

function author_post_count( $author_id ) {
  $post_count = get_transient( 'author_post_count_' . $author_id );
  if ( empty( $post_count ) ) {
  
    global $wpdb;
  
    $post_count = get_var( "SELECT SUM( meta_value ) FROM $wpdb->postmeta WHERE meta_key = 'post_view_count' AND post_id IN (SELECT ID FROM $wpdb->posts WHERE post_author = $author_id AND post_type = 'post' AND post_status = 'publish' ) " );
    set_transient( 'author_post_count_' . $author_id, $post_count, DAY_IN_SECONDS / 2 );
  }
  return $post_count;
}

The beauty of this is that we didn’t need to modify anything on the front end, only the function that calculates the post counts. We set the expiration to every 12 hours, after which the number will be refreshed. Readers still see a nearly up-to-date statistic while our server has a breather.

Conclusion

I hope you agree that transients are fantastic. They can give your site tremendous speed increases, not to mention that methods like this can stave off costly server upgrades.

One thing to be careful with is to not get carried away. You don’t have to cache everything just because you can. There are other factors that come into play (like WordPress loading all postmeta when you call the get_post_meta() function) and in many cases adding lots and lots of lines of code is detrimental to overall code quality.

Identify the main “leaks” in your site’s performance and patch them up with transients to get the most out of your server and minimize load times for your users.

Daniel builds plugins, themes and apps – then proceeds to write or talk about them. He's the editor for the WordPress section at Smashing Magazine and he writes for a bunch of other online magazines. When not coding or writing you'll find him playing board games or running with his dog. Drop him a line on Twitter or visit his personal website. More articles by Daniel Pataki
  • cheh

    Hi, Daniel!

    Very interesting a post. You offer to store temporary cached footer with menu. But how to deal with a `.current_page_item` class which typically used for styling current menu item?

  • This is actually very useful. It is a technique we use for our dynamically generated content but it never occurred to me to use it for something ‘static’ like a footer, although this makes perfect sense, especially in a WordPress site we have created for one of our clients that has just a high-level navigation in it… Thank you.

  • Gaurav Gandhi
Home CSS Deals HTML HTML5 Java JavaScript jQuery Miscellaneous Mobile MySQL News PHP Resources Security Snippet Tools Tutorial Web Development Web Services WordPress