Technology13 minute read

Five Battle-tested Techniques Your WordPress API Developer Isn't Using

Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

One of the best ways to elevate your status as a WordPress developer, at least in the eyes of your clients, is to become skilled at writing API consumers.

In this article, Toptal Freelance WordPress Developer Scott Fennell guides you through five techniques that will help you create powerful WordPress API clients to perfectly suit your needs.

Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Scott Fennell
Verified Expert in Engineering
15 Years of Experience

Scott has written hundreds of WordPress themes and plugins. He specializes in third-party API integration and he’s an active tech writer.


One of the best ways to elevate your status as a WordPress developer, at least in the eyes of your clients, is to become skilled at consuming APIs. Here’s a common scenario for WordPress API implementation: Your client asks you to add a widget to their site—say, for example, an email subscription widget. You grab some code from their third-party email service—perhaps it’s a script tag or an iframe—paste it into the page, and reply to your client, “Got it!”

Unfortunately, you are dealing with a somewhat more demanding client and they notice the following imperfections:

  • Although the widget, like the rest of the site, features a sans-serif font, it’s not quite the right one. The widget is using Helvetica instead of the custom font you’ve installed.
  • The widget’s subscription form triggers a new page load, which can be disruptive if placed halfway through an article.
  • The widget seems to take an extra moment to load after the rest of the page, which feels jarring and cheap.
  • The client would like subscribers to be tagged with metadata based on the post from which they subscribed, and the widget does not offer anything remotely resembling this functionality.
  • The client finds it annoying that they’ll now have to manage two dashboards (wp-admin and the admin area for the email service).

At this point, one of two things might reasonably happen. You could declare these items “nice-to-haves” and reassure your client about the merits of an 80/20 solution, or you could deliver on those requests. In my personal experience, I’ve found that delivering on such requests—that is, demonstrating mastery of third-party services—is a reliable way to convince the client that you are a WordPress wizard of sorts. Plus, it’s often fun to do.

Over the past decade, I’ve used WordPress as a platform for API consumption against probably 50 different APIs. Some of the most common APIs have been MailChimp, Google Analytics, Google Maps, CloudFlare, and Bitbucket. But what if you need to do more, what if you need a custom solution?

How To Develop a WordPress API Client

In this article, I’m going to develop against a generic “email service” API, trying my best to keep things as agnostic as possible. However, I do feel it’s reasonable to assume that we’re dealing with a JSON REST API. Here are some background topics which might help you enjoy the technical points in this article:

If you find yourself marginally familiar with these topics and are interested in digging deeper, pause right now and download the excellent Postman application. It allows you to communicate with APIs without writing code.

Screenshot of Postman's dashboard

Postman. Maybe my favorite development tool?

However, if you’re not at all familiar with those, keep reading anyway. A technical audience with some degree of WordPress experience will get the most out of this article, but I will take care to explain the value of each technique in a less technical way. A non-technical reader will leave this article able to assess the ROI of each point prior to sponsoring it and judge the quality of implementation once delivered.

Note: In case you need a quick refresher course, you can check out our WordPress REST API guide.

With no further preamble, allow me to share with you a handful of different techniques that I find myself appreciating on most every API, project, and team with which I work.

Transients: When to Hold Them, When to Fold Them

In my opening paragraph, I noted that the client found it annoying to straddle two admin areas: wp-admin and the dashboard for their email service. A good way to resolve that would be to provide them with a dashboard widget in wp-admin, for showing a digest of their recent subscriber activity.

Screenshot of the wp-admin dashboard widget

An example of the type of dashboard UI that we might provide within WordPress, to save our clients a trip to their third-party email service provider.

But then again, this could require multiple HTTP requests to the remote API (the API provided by the email service), resulting in long page loads. The solution to this performance problem is to store API calls as transients. This Codex article provides a great explanation that you should definitely read, but I’ll summarize it thusly:

  1. Get the data from the remote API.
  2. Store it using set_transient() with an expiration time of your choosing based on your own judgement about performance, rate limits, and the margin for error in displaying outdated data in this particular application.
  3. Proceed in your business logic—processing the data, returning a value, whatever the case may be.
  4. When you need the data again, such as on the next page load, check for it in the transient cache using get_transient() before concluding that you need to get it from the API.

I consider this to be a helpful and viable foundation, but you can take it a step further if you think for a moment about REST verbs. Of the five most common methods (GET, POST, PATCH, PUT, DELETE), only one of those belongs in your transient cache. Can you guess which one? It’s GET. In my plugins, I almost always have a PHP class dedicated to abstracting calls to the remote API in question, and an argument when instantiating that class is the HTTP method. If it’s not a GET call, then I’m not going to invoke any caching layer at all.

Furthermore, if it’s not a GET call, then it stands to reason that I am taking some action to alter the remote data in some way, perhaps by adding, editing, or removing an email subscriber. This might be a good time to invalidate the existing cache for that resource, via delete_transient().

To return to our example of a WordPress email subscription API, here’s how this would work in practice:

  • A dashboard widget for showing recent subscribers is going to call the API endpoint for /subscribers via a GET request. Because it’s a GET request, it gets stored in my transient cache.
  • A sidebar widget for subscribing to the email list is going to call the API endpoint for /subscribers via a POST request. Because it’s a POST request, not only is it going to avoid my transient cache, it’s going to provoke me to delete the relevant part of my transient cache, so that the dashboard widget reflects this new subscriber.
  • When naming transients, I often organize them by literally naming them after the remote API URL that I am calling. This is a handy way to identify the correct transient to delete. If it’s an endpoint that takes arguments, I’ll concatenate those into a string and add them to the transient name as well.

As a client or other less technical stakeholder, you should be specifically requesting transient caching—or at the very least a discussion thereof—any time the application is pulling data from a remote service. You should familiarize yourself with the excellent Query Monitor plugin to get a view on how transients are working. It will give you an interface for browsing what data is being stashed as a transient, how frequently, and for how long.

Sometimes Transients Aren’t Good Enough

Some premium WordPress hosting services actually do not allow you to use transients in production. They have code running, perhaps in the form of an MU plugin or some other script, that will intercept your attempt to use the transients API and store that information via the object cache instead. WP-Engine, in its most common configuration, is a prime example of this.

Screenshot of the phpMyAdmin view described in the caption

An alarming sight in the phpMyAdmin UI: A production site completely devoid of transients? This likely means object caching is at work.

If you are simply storing and retrieving data, you actually don’t have to care about this and may never even notice it’s happening. The entire family of *_transient() functions will provide you with the same end result, just filtered to use the object cache instead of the transient cache. Where you might run into problems, though, is when attempting to delete transients. Here’s why.

If your API integration is complex enough to merit its own settings page, you may wish to include a UI to allow the admin user to clear the entire transient cache for your plugin. The most common use for this button would be for when the client changes some data directly on the remote service, and wants to invalidate the cache that we’re storing in WordPress. This button might also come in handy if the client changes account credentials, API keys, or just generally as a “factory reset” button for debugging.

Screenshot of option buttons

An example of a UI for allowing the client to empty the local cache for their API data.

Even if you were smart enough to namespace all of your transient keys so that you have some hope of identifying each of them for delete_transient(), the best-case scenario probably still involves raw SQL, which I always try to avoid in WordPress:

// 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'
  $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 );

Not convenient, not efficient. Instead, this situation calls for object caching because object caching gives us a convenient way to group cached values together. This way, when you need to empty all of the cached values related to your plugin, it’s a simple one-liner call to wp_cache_delete( $key, $group ).

I’d summarize all of this by saying: You can’t be an expert at consuming APIs if you are not yet an expert at managing the cache for that data.

As a client, the key thing to watch out for is aberrant cache behavior between staging and production environments. In other words, although testing a new batch of work in staging is always a good practice, caching is something that must also be tested in production with equal care.

The Remote API Can Help Inform Your PHP Class Hierarchy

When laying out the various PHP classes for my plugin, I often find it helpful to mimic the way the API endpoints are defined—for example, what do the following endpoints seem to have in common?

They all return collections, by which I mean the result of a GET request, returning zero-to-many results where each result is a member of an array. That might sound fairly obvious, but I find it to be a helpful prompt for the following class structure in my PHP code:

  • class.collection.php, an abstract class
  • class.subscribers.php extends the abstract class, Collection.
  • class.lists.php extends the abstract class, Collection.
  • class.campaigns.php extends the abstract class, Collection.

The abstract class would take as its only argument an array of query parameters: things like pagination, sort column, sort order, and search filters. It would have methods for common tasks like calling the remote API, handling errors, and perhaps morphing the results into an HTML <select> menu or a jQueryUI AutoSuggest. The classes that instantiate the abstract class might be quite short, perhaps doing little more than specifying the string to use in the *.json API endpoint URL.

Screenshot of the Mailchimp API playground

Mailchimp publishes an API "playground" for sandboxing API calls and such. It also acts as a convenient way to surf the entire data hierarchy of their API, giving us a helpful glimpse into how we might structure our own class hierarchy.

Similarly, what do the following endpoints have in common?

They all return an item, by which I mean exactly one specific, unique member of a collection: things like one particular email subscriber, one email list, or one email campaign. Therefore, I like to use the following structure in my PHP code:

  • class.item.php, an abstract class
  • class.subscriber.php extends the abstract class, Item.
  • class.list.php extends the abstract class, Item.
  • class.campaign.php extends the abstract class, Item.

The abstract class would take as its only argument a string to identify the specific item being requested. Once again, the classes being instantiated might be quite short, perhaps doing little more than specifying the string to use in */duy736td.json.

There are many approaches to structuring class inheritance, but even if you take a different approach to what I’ve outlined above, I bet there is a good chance that the structure of the remote API can help inform the structure of your application.

As a client, a common symptom of poor architecture is when you find yourself having to request the same change over and over across an application. For example, if you request that reports return 100 results per page instead of 10, and you have to keep repeating that request for subscriber reports, campaign reports, unsubscribe reports, etc, you may be detecting poor class architecture. In this situation, it’s worth asking your team if they would benefit from a refactoring cycle: A body of work where the goal is not to change the behavior of the product but rather to improve the underlying code so that it become easier to change the behavior of the product in the future.

The Perfect Use-case for WP_Error

I’m embarrassed to admit that it took me years longer than it should have to properly begin using the WP_Error family of functions in my code. I tended to just code my way through, either assuming there’d never be errors worth caring about programmatically or handling them on a case-by-case basis. Working with remote APIs cuts through that mentality like a laser beam, because it presents a supremely convenient and powerful use case for using WP_Error.

Recall earlier that I mentioned I often have a PHP class whose purpose is to make HTTP requests to the remote API. When you peel back all the boilerplate, all the data manipulation, all the secondary concerns, that class really comes down to calling wp_remote_request() so as to get an HTTP response object from the API. Conveniently, wp_remote_request() will instead return a WP_Error if the call fails to execute for some reason, but what about if the call succeeds in returning an HTTP response of an unfavorable type?

Screenshot of a subscription form

Technically speaking, the API call worked, but it's not exactly free of caveats. These caveats need to be captured and reported in a consistent way throughout the codebase.

As an example, maybe we made a call to the /lists.json endpoint, but this particular account does not yet have any lists set up. This would return a valid HTTP response, but with a status code of 400. While that’s not exactly a fatal error per se, from the perspective of some front-end code that wants to turn this API call into a dropdown menu, a 400 might as well be a WSOD! Therefore, I find it helpful to do some additional parsing on the result of wp_remote_request(), potentially returning a WP_Error after all:

	function call() {
		$response       = wp_remote_request( $this -> url, $this -> args );
		$code           = wp_remote_retrieve_response_code( $response );
		$first_digit    = $code[0];
		$good_responses = array( 2, 3 ); 
		if( ! in_array( $first_digit, $good_responses ) {
			$body = wp_remote_retrieve_body( $response );
			$out  = new WP_Error( $code, $body );
		} else {
			$out = $response;
		return $out;

This pattern can help simplify the code that invokes our caller class, because we know we can safely rely on is_wp_error() before proceeding with our output.

As a client, you should occasionally play the role of a malicious user, a confused user, and an impatient user. Use the app in ways it was not meant to be used. Do the things your developers don’t seem to want you to do. Take note of what happens. Do you get helpful error messages? Do you get any error messages at all? If not, it may be worth sponsoring a body of work around better error handling.

The Beautiful Debugging Power of ob_get_clean()

The modern programmable web, where nearly every site consumes the APIs of other sites, and is itself consumed via its own API, has become an incredibly powerful arena for code. But it’s precisely this quality that can also make it quite slow.

It’s common for remote HTTP requests to be the most time-consuming parts of a given page load. For this reason, many API-driven components execute either via Ajax or cron. For example, an autosuggest for searching through a list of email subscribers should probably ping the remote data source on demand, upon each keystroke, rather than loading all 100,000 subscribers within the DOM as the page loads. If that’s not an option, perhaps a large query could synchronize on a nightly cron task, so that results can be pulled from a local mirror rather than the remote API.

The problem with this approach is that it can be difficult to debug. Instead of simply turning on WP_DEBUG and letting the error messages roll into your browser window, you’re stuck looking in the browser network console, or tailing a log file as a cron task (hopefully?) is executing. I find this uncomfortable.

One way to improve this situation is to make careful and strategic calls to error_log(). But then again, a common problem with logging is that with large or busy applications, error logs can grow too large, or grow too quickly, to be useful for monitoring or parsing. Therefore, we have to be selective with what we log, putting just as much thought into that, as we do with our actual application logic. It’s a pity to have taken the time to log some exotic edge case error that only seems to occur intermittently on some infrequent cron task only to realize that the true nature of the bug has evaded you once again because you failed to log some particular array member, say, of the offending value.

Therefore, my philosophy has become, I don’t always log, but when I do, I log everything. In other words, after identifying a particularly worrisome function, I’ll log it with as wide a net as possible:

function debug( $bug ) {
	var_dump( $bug );
	$out = ob_get_clean();
	error_log( $out );

This amounts to var_dump()‘ing the entire buggy value into a single entry in the error log file.

Screenshot of the error log file

An error log that has grown too large to be ergonomic for debugging.

As a client, it’s worth checking periodically on the total file memory usage for your application. If you notice that you’re suddenly butting up against the storage limits in your hosting account, there’s a good chance that an error log gone wild is to blame. Your developers will benefit from a work cycle focused on better logging—and your customers will as well!

Not Exactly Clickbait, But It’ll Do

Please forgive the listicle structure of this article. I couldn’t force these points into a more unifying article theme because these patterns are very generic: They apply to any JSON REST endpoint and any WordPress output.

They are the patterns that I see occurring over and over, regardless of what the remote API is, or what we’re using it for within WordPress. I’ve gone so far as to collect all of these sorts of principles into a plugin boilerplate that greatly accelerates my work. Do you have similar points that you retain for each project? Please share them so I can steal them and add them to my boilerplate!

Understanding the basics

  • What is the WordPress REST API?

    A convenient way for using WordPress to publish an API. This API can be consumed by other WordPress sites, other non-WordPress sites, or even the publishing site itself. This is a popular approach for using WordPress as a “headless” CMS or even just for small-scale Ajax listeners.

  • What is an API key on WordPress?

    API keys are a common way to manage authentication. The WordPress API is compatible with several methods for authentication. One of these is the WordPress REST API OAuth plugin, which gives users an interface for managing API keys.

  • What is WP-JSON?

    WP-JSON can be thought of as a sibling of WordPress’s RSS view along with its normal front-end view. It’s another way of outputting WordPress data, although most human readers would not want to consume WordPress in this format. Rather, its purpose is to be consumed by API clients.

Hire a Toptal expert on this topic.
Hire Now
Scott Fennell

Scott Fennell

Verified Expert in Engineering
15 Years of Experience

Portland, ME, United States

Member since February 20, 2017

About the author

Scott has written hundreds of WordPress themes and plugins. He specializes in third-party API integration and he’s an active tech writer.

authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

Join the Toptal® community.