The Deal with WordPress Transients

Avatar of Scott Fennell
Scott Fennell on (Updated on )

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

In this article we dig into an important type of caching that is available to you in WordPress: transients. Like any cache, you use transients to store any kind of data that takes a long time to get, so the next time you need it, it returns super fast. The beauty of transients is they clean up after themselves, as long as you watch out for the pitfalls described here!

This post has been updated to reflect a change to the character limit when naming transients. Much thanks to Ian Dunn for reporting this.

The web has gotten really “API-ish”. By that I mean, almost every major site is pushing and pulling content to and from other sites. Consider:

  • A near-majority of ESPN.com seems to actually be Twitter.
  • Programmable Web is now tracking over 13,000 API’s.
  • One of the biggest initiatives around WordPress for many months has been the WP-API.

In my own work, I’ve seen client and employer expectations grow to include the hope — nay, the assumption — that my skill set includes API integration. It’s not enough to paste in an email signup form as an <iframe>. Rather, my agency needs them abstracted into a [shortcode] for easier maintenance. It’s not sufficient to paste in a product slider via some 3rd party JavaScript, rather the designer expects precise control of the animations. Having a developer log in to a CDN to see if a domain is in development mode is too time-consuming, so we call the CDN and display the status in the dashboard.

WordPress provides with us with some phenomenal tools for API integrations. One of these is the Transients API. It’s one of my favorite things that ship with WordPress, and I’d like to share a few tricks with you. If you are new to transients in general, read on. If you already get the concept and want to skip to the nuts and bolts, feel free to do so.

One more thing: I’m going to assume you are familiar with making HTTP requests from a WordPress plugin, as it would be beyond the scope of this article to lay that out in detail. My examples will all use the WordPress HTTP API for making requests.

What do transients have to do with API integrations?

Any time you are calling a remote server, there’s some extra latency, sometimes a quite a bit of extra latency. Transients allow you to cache the response that you get from the remote API, storing it nearby in your WordPress database (well, usually in the database; more on that later). Also, many API’s have a rate-limit, meaning you are only allowed to make x amount of requests within a given time period. Transients allow you to request that API response from yourself, saving you many, many remote calls.

What are transients, exactly?

They’re a way to cache information. Transients are a form of caching that takes place on the server, as opposed to browser caching. Think of a transient as an organism that has three components:

  1. A key. A short string of text. The name of the organism.
  2. A value. Any php variable. The body — the guts, if you will — of the organism.
  3. A lifespan. Often expressed as a time constant such as DAY_IN_SECONDS. The amount of time for which we want this organism to live.

Transients are very similar to WordPress options, only transients have a designated lifespan. Because of that, they are an excellent choice for storing the result of remote calls.

Huh… So what if the remote API changes its response? And won’t all these transients clutter up my database?

That’s the cool thing about transients: They expire automatically. If you attempt to retrieve a transient from your database after it has expired, WordPress will automatically delete it, preventing any clutter. At the same time, it will re-create it anew, ensuring that you have (reasonably) fresh content from the remote API.

Whoa, wait, WordPress will delete my valuable information?

Yeah, it will. Transients are absolutely not for storing data that can’t be automatically re-created. Therefore, you wouldn’t use a transient to store, for example, data that a user is entering in a form on your site.

Are transients just for remote calls?

Transients are for any chunk of information that takes a long time to generate. They will improve server latency for any routine more complex than retrieving a single database cell. That said, they are more code, and more code means more bugs. Therefore, I tend to reserve them for either remote calls or really large queries.

How does this work exactly?

Let’s say you want a list of recent subscribers to your email newsletter. You use a third party service for email updates, and it exposes your list of subscribers via an API. Here’s the sequence:

  1. Right now, you’d have to call the API to ask it for the list of subscribers, since you don’t have that information handy.
  2. The API responds to your request with the list of subscribers, and although it takes a few hundred milliseconds, you get the API response you need.
  3. You store that data on your server, in a transient, giving it a lifespan of HOUR_IN_SECONDS, meaning one hour.
  4. For the next hour, every time a user loads your list of recent subscribers, it’ll happen really quickly, since it’s all stored in one big pile, in one cell in your database.
  5. An hour goes by and a user tries to load your page. WordPress notices that the lifespan has expired, it deletes your local value, and you’re back to step one of this process.

I still don’t get it.

The WROX book on plugin development has a fantastic chapter on transients, as does the codex.

Okay, I get it. Snippet me.

Here’s what transients look like in their most basic form:

<?php
/**
 * Get a list of email subscribers.
 *
 * @return object The HTTP response that comes as a result of a wp_remote_get().
 */
function css_t_subscribers() {

  // Do we have this information in our transients already?
  $transient = get_transient( 'css_t_subscribers' );
  
  // Yep!  Just return it and we're done.
  if( ! empty( $transient ) ) {
    
    // The function will return here every time after the first time it is run, until the transient expires.
    return $transient;

  // Nope!  We gotta make a call.
  } else {
  
    // We got this url from the documentation for the remote API.
    $url = 'https://api.example.com/v4/subscribers';
    
    // We are structuring these args based on the API docs as well.
    $args = array(
      'headers' => array(
        'token' => 'example_token'
      ),
    );
    
    // Call the API.
    $out = wp_remote_get( $url, $args );
    
    // Save the API response so we don't have to call again until tomorrow.
    set_transient( 'css_t_subscribers', $out, DAY_IN_SECONDS );
    
    // Return the list of subscribers.  The function will return here the first time it is run, and then once again, each time the transient expires.
    return $out;
    
  }
  
}
?>

That’s the basic routine: Check for the value locally, if you have it great, if not, grab it remotely and store it locally for next time.

But I was promised tricks.

Yes! I have some tricks to share now that you’ve seen the basics. I’m not going to bundle this up into a final example, because your treatment will likely need be tailored to your application. This is a grab bag, and I’m going to organize it around the three components of a transient that I explained earlier:

  1. The name.
  2. The content.
  3. The lifespan.

Intriguing: These are also the three values that get passed to set_transient().

Tricks for naming your transient

This is by far the deepest part of my grab bag of tricks when it comes to transients. It’s a little counter-intuitive, but naming your transients is the hardest thing about using them. The way you name your transients can open up a number of powerful opportunities, or break your plugin altogether.

Prefixing your transient names

It’s helpful to be able to identify all of the transients that pertain to your plugin. The way to do this is to prefix them with your plugin namespace. This is also crucial for preventing collisions with other transients. That’s why you see me doing `css_t_subscribers` instead of just `subscribers` in most of my examples here, where `css_t` is my imaginary prefix for css-tricks.com.

Changing the name in order to break the cache

There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors. — Leon Bambrick’s, riff on the Phil Karlton quote

Amazing! Out of the three hard problems in computer science, naming your transient involves two of them: Naming things, and cache invalidation. Naming your transient relates to cache invalidation because if you change the name of your transient, then WordPress won’t be able to find it. This can be actually be a good thing, because it forces your plugin to call the remote API to get fresh data.

An occasion for this might be when you release a new version of the plugin. It stands to reason that if the plugin code has changed, then you would want your code to grab refreshed transients instead of continuing to serve the old ones that assume the old plugin code. One way to do this is to include the version number of your plugin in your transient name:

<?php

  // This is the version number for our plugin.
  CSS_T_VERSION = '3.1.4';

  function css_t_subscribers() {

    // Do we have this information in our transients already?
    $transient = get_transient( 'css_t_subscribers' . CSS_T_VERSION );
  
    // Do things...
    
    // Save the API response so we don't have to call again until tomorrow.
    set_transient( 'css_t_subscribers' . CSS_T_VERSION, $out, DAY_IN_SECONDS );
    
  }
  
?>

I like to use a constant because constants are global, so I don’t have to pass it into my function — it’s already there. However, I make sure to prefix the constant itself, it in order to avoid collisions with other constants.

But then, out of nowhere, a pitfall! Since your transient name is changing each time you update your plugin, WordPress is never again going to have occasion to call your transients by their old names. This means they will never be deleted! Your transients will stay in your database forever, achieving the dreaded “clutter” status that people sometimes worry about when it comes to transients. That’s one reason why it’s important to prefix all of your transients. Given that prefix, you could automatically delete all of the transients that pertain to your plugin, perhaps even providing a helpful “purge cache” button in your plugin settings page. This is a bit of a delicate matter, so I’m going to save the details for later on in the article.

Let me come clean: I have never set up a test scenario to confirm that transients might someday build up as clutter in the manner I’ve described above, but it does seem to happen in some cases. Furthermore, a reading of the core `get_transient()` function would seem to support this hypothesis. WP Engine seems to agree as well.

Also worth noting: You could grab your plugin version dynamically, from the docblock at the top of your plugin, via get_plugin_data(). However, that script only loads in wp-admin. I would imagine that you could include it on the front end as well, although I haven’t got around to trying that. I can’t vouch for it.

Using magic constants for naming your transients

Php ships with some helpful variables called magic constants, the most useful of which are __CLASS__ and __FUNCTION__. These constants return a string for the name of the current php class, and the name of the current function, respectively. This can streamline your code when it comes to naming lots of things, including transients:

<?php

class CSS_T {

  function subscribers() {

    $transient_key = __CLASS__ .'_' . __FUNCTION__;

  }

}

?>

You could easily combine this technique with the version number technique noted above.

Including your remote API key in your transient name

Most API’s require you to create a unique API key, or some other way to associate your remote calls with your account. If you find that you need to change that API key, it stands to reason that your transients would want to point to remote calls using only the new key. For this reason, you might append your API key to your transient name:

<?php

function css_t_subscribers( $limit = 50 ) {

  $api_key = 'W$3Th&j8Ias76gF%^Fukg3%$Dy3ghd!@';

  $transient_name = __FUNCTION__ . '_' . $api_key';

}

?>

You might be concerned about having your API key flying around cyberspace, as this could present a security concern. You could mitigate that by encrypting the API key, an approach that happens to have other benefits which I’ll discuss shortly.

Including the remote url and request parameters in your transient name

A remote API is going to serve a different response based on the exact url you are querying. Some examples might be:

<?php
// Getting all subscribers VS getting just 50 of them.
$subscribers       = 'https://api.example.com/subscribers';
$fifty_subscribers = 'https://api.example.com/subscribers?limit=50';

// Getting all campaigns VS getting just the ones that have already sent.
$campaigns      = 'https://api.example.com/campaigns';
$sent_campaigns = 'https://api.example.com/campaigns?status=sent';
?>

It’s very likely that your plugin will want to send many combinations of these parameters, so you’d expose them as function arguments. Given that, you can use those values to dynamically build unique transient keys for each query:

<?php

function css_t_subscribers( $limit = 50 ) {

  // The base url for getting subscribers.
  $url = 'https://api.example.com/subscribers';
  
  // Sanitize the limit variable.
  $limit = absint( $limit );
  
  // Add the limit variable to the url.
  $url = add_query_arg( array( 'limit', $limit ), $url );
  
  // Use the url in the transient name.
  $transient_name = __FUNCTION__ . '_' . $url';

}

?>

CAUTION! There is a problem!

Man, we are like drunk with appending stuff to our transient keys here! It is absolutely reasonable to have all of these elements in your transient name:

  • Plugin namespace.
  • PHP class name.
  • PHP function name.
  • Plugin version number.
  • Remote API url.
  • Remote API request parameters.
  • Remote API key.

You could end up with a transient name that is well over 100 characters long, and that won’t work. Why not? Because if you make your transient key longer than 40 characters, WordPress might not store the transient. This is because of a character limit in the options table in the WordPress database. It can be really, really easy to exceed this limit once you start prefixing, adding a version number, and adding some args. WordPress might increase this limit to 255 chars soon, but until then, the way to sidestep this issue is to compress your transient name via PHP’s md5() function.

Reader Jibran B writes in to say: “Transient names (option_name) in wp_options table can now be 172 characters instead of 40.”

md5() can take virtually any string and compress it down to 32 characters — a new string that is guaranteed to be unique to the string you fed it. The result is basically unreadable (it’s a hash) but there’s no reason you would need to read the transient key names, other than the prefix portion.

Given that we have as little as 40 characters to work with, and md5() uses up 32 of them, that means we only have 8 left for our prefix. For the sake of code readability, I take a bow toward the third hard problem in computer science, off by one errors (see above), and give myself only 7 characters, just to be safe:

<?php

function css_t_subscribers( $limit = '50' ) {
  
  // The namespace for our plugin.  
  $namespace = css_t_namespace(); // Let's say this gives us the slug name, 'css_tricks';
  
  // Cut it down to a max of 7 chars.
  $namespace = substr($namespace, 0, 7 );

  // The base url for getting subscribers.
  $url = 'https://api.example.com/subscribers';
  
  // Sanitize the limit variable.
  $limit = absint( $limit );
  
  // Add the limit variable to the url.
  $url = add_query_arg( array( 'limit', $limit ), $url );

  // Build a transient name that is guarenteed to carry all the uniqueness we might want, and also be less than 40 chars.
  $transient_name = $namespace . md5( $url );

}
As of WordPress 4.4, the character limit has been raised to 172 which is likely more than enough for most cases. That said, I don’t have any plans to stop hashing my transient names. I don’t think there’s much gained by not hashing them, there’s still some chance that 172 may not be enough, and the point I made about security is still valid.

Who knew we could go on so long on the niche topic of naming transients. It’s amazing how deep you can go with this, and it’s all because the name can be changed in interesting ways so as to break the cache. But enough about names.

Tricks for storing data in a transient

Earlier in this article, I stated that transients have three components: A name, content, and a lifespan. It’s time to look at the second portion, which is the content that you’re caching in the transient.

It doesn’t have to be a string

WordPress core tells us that we don’t need to serialize our transient content before we store it. In other words, we aren’t limited to storing simple values like strings or numbers. Rather, we can store entire arrays or objects, such as an HTTP response that comes as a result of wp_remote_request().

That said, just because you can store the entire response, that doesn’t necessarily mean you should. It might help streamline your plugin if you parse the response a bit and only store the body, or even some subset of the body. Alternatively, maybe you have a good reason for storing the entire response, maybe because you want to react to the HTTP status code elsewhere in your plugin. It’s up to you.

Not all data is worth storing

Speaking of HTTP status codes, one of the first things I’ll do when making an API integration is read the documentation and curate a list of HTTP status codes. In many API’s, a status code in the 40x or 50x range means that I made a mistake in my plugin code, making a request that the API could not fulfill. There’s probably no reason to store that in a transient, so I’ll compare the response code to my list before saving:

<?php

// Get a list of subscribers from a remote API.
function css_t_subscribers() {
  
  // Transient stuff...

  // Call the remote service.
  $response = wp_remote_get( $url );
  
  // Check our response to see if it's worth storing.
  if ( ! css_t_check_response( $response ) ) {
    return FALSE;
  }
  
}

// Given an HTTP response, check it to see if it is worth storing.
function css_t_check_response( $response ) {

  // Is the response an array?
  if( ! is_array( $response ) ) { return FALSE; }
    
  // Is the response a wp error?
  if( is_wp_error( $response ) ) { return FALSE; }

  // Is the response weird?
  if( ! isset( $response['response'] ) ) { return FALSE; }
    
  // Is there a status code?
  if( ! isset( $response['response']['code'] ) ) { return FALSE; }
    
  // Is the status code bad?
  if( in_array( $response['response']['code'], css_t_bad_status_codes() ) ) { return FALSE; }

  // We made it!  Return the status code, just for posterity's sake.
  return $response['response']['code'];

}

// A list of HTTP statuses that suggest that we have data that is not worth storing.
function css_t_bad_status_codes() {
  return array( 404, 500 );
}

?>

Only storing the result of GET requests

I’m talking about RESTful API’s here. In a restful API, you can make a request using different request types. Here are some of the most common:

  • GET – Used for getting data.
  • POST – Used for adding a row of data.
  • PUT – Used for editing an entire row of data.
  • PATCH – Used for editing part of a row of data.
  • DELETE – Used for deleting an entire row of data.

I keep talking about the wp_remote_request() family of functions, and guess what? They allow you to specify which type of request you’re making. There is only one type of request whose response belongs in a transient, and that’s a GET request. In fact, if you are making any other type of request, then you are likely trying to change data on the remote server, and that means that some of your transients might now be obsolete. This would be an occasion to dump all of the transients related to your plugin. I’ll dig into how we might do that shortly.

In the example of our email API integration, every time someone signs up for my email list, that’s my plugin sending a POST request to the remote API, to add them to my mailing list. I probably have a function in my plugin dedicated to calling that API. That function is going to detect what type of request I’m making and, if it’s not a GET request, it’s going to dump all my plugin transients.

This attitude assumes that data accuracy is more important than performance, and frankly that’s not always going to be the case. Maybe you have a view that offers many hundreds of rows of data, and that data changes very frequently. In such a case, it would not be performant to be dumping your transients on every POST request made by your plugin.

Tricks when designating a lifespan for your transient

We’re on to the third and final part of a transient: The lifespan. This can be expressed in a few different ways:

  • set_transient( $name, $content, 3600 ) – store the data for 3600 seconds, which is an hour.
  • set_transient( $name, $content, 60 * 60 ) – store the data for 60 minutes, which is an hour, only more readable.
  • set_transient( $name, $content, HOUR_IN_SECONDS ) – store the data for an hour, eminently readable. These ship with WordPress.

The “Mayfly” transient for debugging

A Mayfly is an insect that has an incredibly short lifespan. Consider the following transient:

set_transient( $name, $content, 1 )

That’s a transient that will only last for one second! This transient is almost guaranteed to never be called from the database. It would have to be generated and then re-requested in less than a second. However, this introduces a helpful way to provide a sort of debug mode in your plugin. If you are trying to debug your code, one of the most common steps is to echo your variables to see if they reflect what you’re expecting. This can be extremely frustrating with transients. You’d have to go into your API calls and comment out the transient logic in order to make sure you get fresh results for debugging, and then remember to un-comment them before deploying. Instead, I do this:

<?php

// If the user is a super admin and debug mode is on, only store transients for a second.
function css_t_transient_lifespan() {
  if( is_super_admin() && WP_DEBUG ) {
    return 1;
  } else {
    return DAY_IN_SECONDS;
  }
}

// Get subscribers, using a dynamic value for the transient time.
function css_t_subscribers() {

  // ...

  $lifespan = css_t_transient_lifespan();
  set_transient( $name, $content, $lifespan );

  // ...

}

?>

That said, if you have transients with a relatively long lifespan, such as DAY_IN_SECONDS, you’re still going to get those old values until tomorrow. Not cool. That’s what you need a way to easily purge all your plugin transients.

The purge

We need to select all of the transients that relate to our plugin, and then use the delete_transient() function to delete each one. In theory, we could delete them via SQL, but it’s usually best to do things closer to the application level, and that rule definitely applies here. I’ll explain why in a bit.

<?php

// Purge all the transients associated with our plugin.
function purge() {

  global $wpdb;

  $prefix = esc_sql( $this -> get_transient_prefix() );

  $options = $wpdb -> options;

  $t  = esc_sql( "_transient_timeout_$prefix%" );

  $sql = $wpdb -> prepare (
    "
      SELECT option_name
      FROM $options
      WHERE option_name LIKE '%s'
    ",
    $t
  );

  $transients = $wpdb -> get_col( $sql );

  // For each transient...
  foreach( $transients as $transient ) {

    // Strip away the WordPress prefix in order to arrive at the transient key.
    $key = str_replace( '_transient_timeout_', '', $transient );

    // Now that we have the key, use WordPress core to the delete the transient.
    delete_transient( $key );

  }
  
  // But guess what?  Sometimes transients are not in the DB, so we have to do this too:
  wp_cache_flush();
  
}

?>

You could call that function when a user clicks a button on your plugin settings page, when new posts are published, or perhaps whenever a widget is saved. It’s up to you!

Notice the last line in that snippet, where I’m calling wp_cache_flush. That’s because our transients might not be in the DB after all. They might actually be in the object cache!

We need to talk about object caching

Have you picked up on my cautious tone at different points in this article, eluding to the fact that transients are not always in the database? It’s because of object caching.

Recently I was trying to debug an API integration for a client. I tried to use phpMyAdmin to inspect transient values in the database, only I couldn’t find any. This is because the client was using object caching: That means their transients did not live in the database!

In order to avoid problems with object caching, all you have to do is CRUD your transients as normal, using set_transient(), get_transient(), and delete_transient(). If object caching is available, it will CRUD them in its own way.

When I say “as normal”, I mean that as opposed to doing an SQL query to handle transients. In my snippet above, I am selecting my transient via an SQL query, which is against the rules, so I have to pay a penalty. I’m opting to pay a penalty in performance and one extra line of code by calling wp_cache_flush(), which dumps the entire object cache. Comprehensive, simple, but heavy-handed. There are a couple of other ways I might choose to pay that penalty instead.

For one, it would probably be smarter to only dump the part of the cache associated with my plugin. The object cache class has methods for doing that. However, this is an article on transients, so I’m not going to deep-dive into object caching.

A different approach would be to register each of my transient keys in an array, and store that array in the database. That way, I could loop through that array and call delete_transient() on each value.

<?php

// Pass the transient key to this function whenever we save a transient.
function css_t_update_transient_keys( $new_transient_key ) {
  
  // Get the current list of transients.
  $transient_keys = get_option( 'css_t_transient_keys' );

  // Append our new one.
  $transient_keys[]= $new_transient_key;
  
  // Save it to the DB.
  update_option( 'css_t_transient_keys', $transient_keys );
  
}

// Call this function to dump our plugin transients.
function css_t_purge() {

  // Get our list of transient keys from the DB.
  $transient_keys = get_option( 'css_t_transient_keys' );
  
  // For each key, delete that transient.
  foreach( $transient_keys as $t ) {
    delete_transient( $t );
  }

  // Reset our DB value.
  update_option( 'css_t_transient_keys', array() );

} 
  
?>

It feels a little goofy to me to be making an extra database call in order to update the option, whenever we save a transient — it’s like two stones for one bird. But it’s not much code, it does not require SQL, and it plays nice with object caching.

If you want to know more about object caching, I’d suggest digging into WordPress core. For example, check out the source code for delete_transient(). You can see it checking for object caching before falling back to the normal WP options API.

Next steps

I wanted to keep this discussion focused on the Transients API, but the reality is that it is best used in conjunction with WordPress’s HTTP API, with a hint of object caching awareness as well. If you are making a plugin that makes remote calls, you should consider using the WordPress HTTP API. Abstract all those remote calls into one PHP class, and that class can easily use the WordPress Transients API before and after calling the remote service. By mastering the use of transients for your remote calls, you bring the entire web of API’s within your grasp, with minimal performance concerns.