A Guide to the WordPress HTTP API: Automatic Plugin Updates

A Guide to the WordPress HTTP API: Automatic Plugin Updates

Tutorial Details
  • Program: WordPress
  • Version: 3.0
  • Difficulty: Advanced
  • Estimated Completion Time: 60 minutes

As you probably already know, WordPress has a mechanism that detects plugins, themes and the WordPress core updates, makes a notification when they are available, pulls information about these updates and enables you to automatically install these updates. This third (and final) part of the WordPress HTTP API series will teach you how to create your own plugin repository to distribute automatic updates for your users.


A Glimpse at Auto-Updates

WordPress has a magnificent auto-update system that notifies you when new versions of the WordPress core, installed plugins or themes are available. The notifications are displayed in the Admin Bar and also on the plugins page where you can get more details about the new version.

To install the new version, you simply hit the “Update automatically” button. WordPress will automatically download the new package, extract it and replace the old files. No FTP, removing old files, and uploading is required.

There is also a dedicated page for updates which can be reached from the dashboard menu. It’s helpful when you want to do bulk updates of multiple plugins instead of updating each one separately. It also has a “Check Again” button which checks for new updates. By default, WordPress does this check every 12 hours.

Under the Hood

Auto-updates are like magic; you are just a click away from getting the latest version. The good news is that the process itself is not complicated or at least understanding it and customizing it for your own use isn’t. There can be many reasons why you want to run your own server to distribute updates for your plugins; the only one I’m thinking of is premium plugins. If your plugin is free and open to the public, you should publish it on the WordPress plugins repository.

Every 12 hours, your WordPress blog will check for new plugin versions and store the request sent and response received in a site transient called ‘update_plugins‘. The following code will display the content of this transient:

add_filter ('pre_set_site_transient_update_plugins', 'display_transient_update_plugins');
function display_transient_update_plugins ($transient)
{
	var_dump($transient);
}

After 12 hours or so, refresh your blog plugins page and you should get a similar output to the following:

object(stdClass)[18]
  public 'last_checked' => int 1333808132
  public 'checked' => 
    array
      'access/access.php' => string '1.0' (length=3)
      'adpress/wp-adpress.php' => string '3.1' (length=3)
          ...
      'wp-paypal/plug.php' => string '1.0' (length=3)
  public 'response' => 
    array
      'akismet/akismet.php' => 
        object(stdClass)[13]
          public 'id' => string '15' (length=2)
          public 'slug' => string 'akismet' (length=7)
          public 'new_version' => string '2.5.5' (length=5)
          public 'url' => string 'http://wordpress.org/extend/plugins/akismet/' (length=44)
          public 'package' => string 'http://downloads.wordpress.org/plugin/akismet.2.5.5.zip' (length=55)
      'update.tmp/plugin.php' => 
        object(stdClass)[12]
          public 'slug' => string 'plugin' (length=6)
          public 'new_version' => string '1.1' (length=3)
          public 'url' => string 'http://localhost/update.php' (length=27)
          public 'package' => string 'http://localhost/update.php' (length=27)

The output will certainly differ from one blog to another, but the contained information is essentially the same:

  • last_checked (int) – The last time the auto-updates check was run (in seconds)
  • checked (array) – The list of checked plugins and their version
  • response (array) – The returned response from the API server

Disabling the 12 Hours Latency

If you are like me, you probably won’t enjoy waiting 12 hours for WordPress to check for new updates when you are doing related debugging and development. For this, you can simply press the “Check Again” button in the updates page. The 12 hour waiting period is defined in WordPress core on Line 147 of the wp-includes/update.php file, it’s the $timeout variable.

Hooking to the “update_plugins” Transient

Since your plugin is not hosted in the WordPress plugin repository, there will be no response from the traditional API. The solution is to build your own API, check for updates; and when a new update is available add the response to the “update_plugins” transient. WordPress will download the new version from a ‘download_url‘ that you specify in the response.

To check for new versions, we need an API server. It’s not really required that your server has PHP or even a server side language, but it’ll help for finer control of the update process. This tutorial assumes you have a remote PHP server but you can convert the code (which is pretty basic) to your favorite language.

To get a better understanding of how the filter works, pick up any plugin from the “checked” list that gets displayed on your blog. For example, the ‘access/access.php‘ plugin, and put the following code in a new or already running WordPress plugin.

add_filter ('pre_set_site_transient_update_plugins', 'display_transient_update_plugins');
function display_transient_update_plugins ($transient)
{
	$obj = new stdClass();
	$obj->slug = 'access.php';
	$obj->new_version = '2.0';
	$obj->url = 'http://anyurl.com';
	$obj->package = 'http://anyurl.com';
	$transient[plugin_directory/plugin_file.php] => $obj;
	return $transient;
}

Now when you refresh your plugins page, you should see a notification that a new version of the plugin is available (version 2.0) and you can get more information or install it. None of that will work yet, obviously, but you get the idea. WordPress relies on this transient to know about the new updates.

In summary, our solution needs to do the following:

  1. Check for new updates from your server.
  2. When a new update exists, add it to the response property of the ‘update_plugins‘ transient.
  3. Hook with the “plugins_api” in a similar fashion to provide the new version information.
  4. An API Server which provide the version, details and package of the latest release.

The Auto-Update Class

To standardize and streamline things, I have built a PHP class which can work with any plugin. It’s quite simple to use, you just need to provide 3 parameters: the plugin’s current version, the update path, and the plugin’s slug. I’ll explain in detail later what each one does.

Our class will be able to perform 3 HTTP requests, 2 required and a third optional:

  1. The first request will check for the latest version on the remote server. To make things simpler and faster the API server will only return the latest version as a string (e.g. “1.0″ or “1.1″)
  2. The second request will return the latest version information as a serialized PHP object.

The class has 3 public functions which you can use in your plugin:

  • getRemote_version – Returns the latest remote version as a string.
  • getRemote_description – Returns the latest remote version details as a PHP object.
  • getRemote_license – Returns the license status of the plugin. It is optional, and there is no implementation of licensing in this tutorial.

Using the Class

The class can be initiated in the “init” action. It has 3 parameters:

  1. Current version – A string with the current installed version of the plugin.
  2. Update path – The path of the update server. In our case, it’s a PHP file in my localhost root directory.
  3. Plugin slug – This is required to get the plugin slug and name. For that, the class should be initiated in the same file that has the WordPress plugin header.
add_action('init', 'wptuts_activate_au');
function wptuts_activate_au()
{
	require_once ('wp_autoupdate.php');
	$wptuts_plugin_current_version = '1.0';
	$wptuts_plugin_remote_path = 'http://localhost/update.php';
	$wptuts_plugin_slug = plugin_basename(__FILE__);
	new wp_auto_update ($wptuts_plugin_current_version, $wptuts_plugin_remote_path, $wptuts_plugin_slug);
}

That’s all! Now your plugin is ready for getting updates from the specified remote path.


The Class Code

Step 1 The Class, Properties, and Methods

We start off with a class skeleton defining the properties, the constructor and the different methods. Each property and method is preceded with a comment section that has a description, the accepted parameters and the returned variable. The comments follow the PhpDoc guidelines.

class wp_auto_update
{
    /**
     * The plugin current version
     * @var string
     */
    public $current_version;

    /**
     * The plugin remote update path
     * @var string
     */
    public $update_path;

    /**
     * Plugin Slug (plugin_directory/plugin_file.php)
     * @var string
     */
    public $plugin_slug;

    /**
     * Plugin name (plugin_file)
     * @var string
     */
    public $slug;

    /**
     * Initialize a new instance of the WordPress Auto-Update class
     * @param string $current_version
     * @param string $update_path
     * @param string $plugin_slug
     */
    function __construct($current_version, $update_path, $plugin_slug)
    {
    }

    /**
     * Add our self-hosted autoupdate plugin to the filter transient
     *
     * @param $transient
     * @return object $ transient
     */
    public function check_update($transient)
    {
    }

    /**
     * Add our self-hosted description to the filter
     *
     * @param boolean $false
     * @param array $action
     * @param object $arg
     * @return bool|object
     */
    public function check_info($false, $action, $arg)
    {
    }

    /**
     * Return the remote version
     * @return string $remote_version
     */
    public function getRemote_version()
    {
    }

    /**
     * Get information about the remote version
     * @return bool|object
     */
    public function getRemote_information()
    {
    }

    /**
     * Return the status of the plugin licensing
     * @return boolean $remote_license
     */
    public function getRemote_license()
    {
    }
}

Step 2 The Constructor

The constructor initiates a new instance of our class. It accepts three parameters which we already defined in the previous section. The constructor sets the “current_version“, “update_path“, “plugin_slug” and “slug” (which it extracts from the “plugin_slug“) public properties. It also hooks to the “pre_set_site_transient_update_plugins” and “plugins_api” filters.

/**
 * Initialize a new instance of the WordPress Auto-Update class
 * @param string $current_version
 * @param string $update_path
 * @param string $plugin_slug
 */
function __construct($current_version, $update_path, $plugin_slug)
{
	// Set the class public variables
	$this->current_version = $current_version;
	$this->update_path = $update_path;
	$this->plugin_slug = $plugin_slug;
	list ($t1, $t2) = explode('/', $plugin_slug);
	$this->slug = str_replace('.php', '', $t2);

	// define the alternative API for updating checking
	add_filter('pre_set_site_transient_update_plugins', array(&$this, 'check_update'));

	// Define the alternative response for information checking
	add_filter('plugins_api', array(&$this, 'check_info'), 10, 3);
}

Step 3 Remote Call Functions

There are 3 functions which make remote requests to our self-hosted repository. They are public functions which can be used independently and each of them either return the response value or false.

getRemote_version – Returns the latest plugin version from our self-hosted repository as a string.

/**
 * Return the remote version
 * @return bool|string $remote_version
 */
public function getRemote_version()
{
	$request = wp_remote_post($this->update_path, array('body' => array('action' => 'version')));
	if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
		return $request['body'];
	}
	return false;
}

getRemote_information – Returns information on the plugin’s latest version from our self-hosted repository as a PHP object.

/**
 * Get information about the remote version
 * @return bool|object
 */
public function getRemote_information()
{
	$request = wp_remote_post($this->update_path, array('body' => array('action' => 'info')));
	if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
		return unserialize($request['body']);
	}
	return false;
}

getRemote_license – Returns the licensing status. This function is optional and there is no licensing implementation in this tutorial.

/**
 * Return the status of the plugin licensing
 * @return bool|string $remote_license
 */
public function getRemote_license()
{
	$request = wp_remote_post($this->update_path, array('body' => array('action' => 'license')));
	if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
		return $request['body'];
	}
	return false;
}

Step 4 Filter Hooks

The class hooks to the “pre_set_site_transient_update_plugins” and “plugins_api” filters. A deeper explanation of each filter and the hook is available in the first section in case you skipped it.

/**
 * Add our self-hosted autoupdate plugin to the filter transient
 *
 * @param $transient
 * @return object $ transient
 */
public function check_update($transient)
{
	if (empty($transient->checked)) {
		return $transient;
	}

	// Get the remote version
	$remote_version = $this->getRemote_version();

	// If a newer version is available, add the update
	if (version_compare($this->current_version, $remote_version, '<')) {
		$obj = new stdClass();
		$obj->slug = $this->slug;
		$obj->new_version = $remote_version;
		$obj->url = $this->update_path;
		$obj->package = $this->update_path;
		$transient->response[$this->plugin_slug] = $obj;
	}
	return $transient;
}

/**
 * Add our self-hosted description to the filter
 *
 * @param boolean $false
 * @param array $action
 * @param object $arg
 * @return bool|object
 */
public function check_info($false, $action, $arg)
{
	if ($arg->slug === $this->slug) {
		$information = $this->getRemote_information();
		return $information;
	}
	return false;
}

The Full Class Code

class wp_auto_update
{
    /**
     * The plugin current version
     * @var string
     */
    public $current_version;

    /**
     * The plugin remote update path
     * @var string
     */
    public $update_path;

    /**
     * Plugin Slug (plugin_directory/plugin_file.php)
     * @var string
     */
    public $plugin_slug;

    /**
     * Plugin name (plugin_file)
     * @var string
     */
    public $slug;

    /**
     * Initialize a new instance of the WordPress Auto-Update class
     * @param string $current_version
     * @param string $update_path
     * @param string $plugin_slug
     */
    function __construct($current_version, $update_path, $plugin_slug)
    {
        // Set the class public variables
        $this->current_version = $current_version;
        $this->update_path = $update_path;
        $this->plugin_slug = $plugin_slug;
        list ($t1, $t2) = explode('/', $plugin_slug);
        $this->slug = str_replace('.php', '', $t2);

        // define the alternative API for updating checking
        add_filter('pre_set_site_transient_update_plugins', array(&$this, 'check_update'));

        // Define the alternative response for information checking
        add_filter('plugins_api', array(&$this, 'check_info'), 10, 3);
    }

    /**
     * Add our self-hosted autoupdate plugin to the filter transient
     *
     * @param $transient
     * @return object $ transient
     */
    public function check_update($transient)
    {
        if (empty($transient->checked)) {
            return $transient;
        }

        // Get the remote version
        $remote_version = $this->getRemote_version();

        // If a newer version is available, add the update
        if (version_compare($this->current_version, $remote_version, '<')) {
            $obj = new stdClass();
            $obj->slug = $this->slug;
            $obj->new_version = $remote_version;
            $obj->url = $this->update_path;
            $obj->package = $this->update_path;
            $transient->response[$this->plugin_slug] = $obj;
        }
        var_dump($transient);
        return $transient;
    }

    /**
     * Add our self-hosted description to the filter
     *
     * @param boolean $false
     * @param array $action
     * @param object $arg
     * @return bool|object
     */
    public function check_info($false, $action, $arg)
    {
        if ($arg->slug === $this->slug) {
            $information = $this->getRemote_information();
            return $information;
        }
        return false;
    }

    /**
     * Return the remote version
     * @return string $remote_version
     */
    public function getRemote_version()
    {
        $request = wp_remote_post($this->update_path, array('body' => array('action' => 'version')));
        if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
            return $request['body'];
        }
        return false;
    }

    /**
     * Get information about the remote version
     * @return bool|object
     */
    public function getRemote_information()
    {
        $request = wp_remote_post($this->update_path, array('body' => array('action' => 'info')));
        if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
            return unserialize($request['body']);
        }
        return false;
    }

    /**
     * Return the status of the plugin licensing
     * @return boolean $remote_license
     */
    public function getRemote_license()
    {
        $request = wp_remote_post($this->update_path, array('body' => array('action' => 'license')));
        if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
            return $request['body'];
        }
        return false;
    }
}

Setting Up Your Server

Now that we have made our plugin ready for self-hosted auto-updates, it’s time to setup the server which will serve the notification, the description and the update package. In our case, we are using PHP with a single file called “update.php” in my local server root directory. The plugin update is “update.zip” in the same directory.

If you did read the auto-update class code, you’ll find that it does 3 requests which should be handled by our API server.

  1. Plugin Version

    • Request Type: POST
    • Parameter/value: “action”/”version”
    • Return value: String (“1.0″, “1.5″, “2.0″…)
  2. Plugin Details

    • Request Type: POST
    • Parameter/value: “action”/”info”
    • Return value: String. A serialized PHP objected
  3. Plugin License

    • Request Type: POST
    • Parameter/value: “action”/”license”
    • Return value: Feel free to implement it :)
  4. Plugin Package

    • Request Type: GET
    • Parameter/value: none
    • Return value: Zip Package

Code for the update.php File

if (isset($_POST['action'])) {
  switch ($_POST['action']) {
    case 'version':
      echo '1.1';
      break;
    case 'info':
      $obj = new stdClass();
      $obj->slug = 'plugin.php';
      $obj->plugin_name = 'plugin.php';
      $obj->new_version = '1.1';
      $obj->requires = '3.0';
      $obj->tested = '3.3.1';
      $obj->downloaded = 12540;
      $obj->last_updated = '2012-01-12';
      $obj->sections = array(
        'description' => 'The new version of the Auto-Update plugin',
        'another_section' => 'This is another section',
        'changelog' => 'Some new features'
      );
      $obj->download_link = 'http://localhost/update.php';
      echo serialize($obj);
    case 'license':
      echo 'false';
      break;
  }
} else {
    header('Cache-Control: public');
    header('Content-Description: File Transfer');
    header('Content-Type: application/zip');
    readfile('update.zip');
}

Customizing the Plugin Information Box

It’s possible to customize the plugin information box. The box has:

  1. Information about the plugin (description, last update, downloads count)
  2. Sections which will add tabs to the information box

Sections can be added to the sections array property. The description tab is itself a section.

$obj->sections = array(
  'description' => 'The new version of the Auto-Update plugin',
  'another_section' => 'This is another section',
  'changelog' => 'Some new features'
);

Summary

I have tried to cover most of the update process in this tutorial. The source code for the Auto-update class can be found in this GitHub repository. Feel free to fork and push your improvements. Suggestions, questions and critiques are certainly welcome.

Abid Omar is omarabid on Codecanyon
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://christophedebruel.be Christophe Debruel

    Excellent, bookmarked :). I need this for a few of my plugins.

  • http://freakify.com Ahmad Awais

    will try it sooner than later. Nice one

  • Pingback: A Guide to the WordPress HTTP API: Automatic Plugin Updates | Qtiva

  • http://www.ruturaaj.com Ruturaaj

    Excellent! I always wondered how to implement this Auto-Update feature for my self-hosted Plugin download-packages. Your text save me a lot of time my friend. I’m going to try it right away.

    One small thing… perhaps slightly off-topic too; but I think you can answer this query: WordPress Plugins page now allows you to set a large header image that’s displayed when User searches for the Plugin and then goes to your Plugin Page on WordPress website. I’m not sure if I’ve conveyed what I’m referring to correctly; but if you’ve followed my words, do you know how to set that image for your plugin?

  • http://www.wordpressready.com Nando

    Brilliant and perfect timing.
    I’m working in a plugin which downloads data and resources from a custom repository. With a few tweaks this code could be adapted to detect new data and also sub-plugins.

  • Pingback: A Guide to the WordPress HTTP API: Automatic Plugin Updates | Shadowtek Hosting and Design Solutions

  • http://imclement.com Clement

    I’m not familiar with HTML API but this Guideline is totally awesome!

  • http://www.deluxeblogtips.com Rilwis

    Great article. I’ve been using this class for the same purpose, but haven’t looked at how it’s implemented. Thanks for you explanation. Just watched your repository in GitHub :)

  • Pingback: Tweet-Parade (no.16 April 2012) | gonzoblog.nl

  • Pingback: Wordpress - Plugins | Pearltrees

  • Pingback: BlogBuzz May 12, 2012

  • Uzo

    Was already watching your repo before I saw this article. Still it’s nice to see your thought process broken down. Thanks for sharing :)

  • Carlson

    Great article, but I found a bug: If you try click on “View version X details.”, It will show a lightbox. So, after appear the lightbox if you try click on “Install Now”, will be showed an error message:

    “An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the support forums.”

    You can fix it, please?

    Thank you very much.

  • Waylon

    Brilliant, thanks a stack! Not a lot of articles regarding this topic around.

  • ruel

    Thanks a lot Man.

  • http://pogidude.com Ryann

    By the way, I’d like to make a suggestion.

    on the function check_info(), might be better to do:

    public function check_info($false, $action, $arg)
    {
    if ($arg->slug === $this->slug) {
    $information = $this->getRemote_information();
    return $information;
    }

    /**
    * Return variable $false instead of explicitly returning boolean FALSE
    * wordpress passes FALSE here by default
    */
    return $false;
    }

    Found this out after using this method on two plugins and I had them both activated at the same time. When you click on the “View version details” link, the popup would say:

    “An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the support forums.”

    This is because both plugins are filtering “plugins_api”. This is what happens when both plugins explicitly returned FALSE if the slugs don’t match.

    when you click on the “version details” link for plugin1, then the check_info() function will return the $information. The problem happens if another plugin (plugin2) is also filtering “plugins_api”. if plugin2′s check_info() function is called last (I only have a rough idea how filters work under the hood. I do know the callback functions are called one by one), then when plugin2, seeing that the call is not for itself returns FALSE, instead of returning whatever value was passed in, will effectively overwrite the plugin info meant for plugin1.

    when a FALSE is returned from this check_info() functions, wp will then check it’s own repository. Since our plugins aren’t there, then it’ll return an error message.

    Hope this helps anyone banging their heads on the keyboard :) poor keyboard.

  • Matt

    Did anyone actually get this to work? The plugins page never returns any update that a new version is ready.

  • Pingback: How to Integrate WordPress Plugin Update Notifications Into Your Commercial Plugin : WPMayor

  • Matt

    @Carlson: I’m having that same issue. If I press “update now” everything works as expected, but when I press “Install Now” in the lightbox I get an error page. It appears that the URLs are different between the two actions too, one has “…install-plugin…” and the other has “…upgrade-plugin…”.

    Did you get this to work, and how?

  • Matt

    For those looking to solve the aforementioned lightbox installation button bug, I was able to fix it by replacing the check_info method with the following:

    public function check_info($false, $action, $arg) {
    if ($arg->slug === $this->slug) {

    $information = $this->getRemote_information();

    $information->slug = $this->slug; //This is the new addition

    return $information;
    }
    return false;
    }

    • Samuel

      It whould be better to make this change in “update.php” file :
      this line : $obj->slug = ‘plugin.php’;
      should be $obj->slug = ‘plugin_dir/plugin.php’;

  • Anton

    Great tutorial!

    Now I am trying to implement the license check. I need to pass the licence key and check it. And if it’s valid I have to make the .zip package on the fly with the proper license file inside it.

    Could you help me with that?

  • http://www.renegadetechconsulting.com/ Renegade Tech Consulting

    Thanks for this.

    Although I am curious as to why you have all of the properties as public. Should they not be private? This would help to encapsulate the class.

  • http://www.paulund.co.uk/ Paul

    Bookmarked this really going to be useful in future.

  • Pingback: Automatic Updating of WordPress Plugins and Core - WPMayor

  • Pingback: Automatic Updating of WordPress Plugins and Core | CMS Radar

  • Pingback: Automatic Updating of WordPress Plugins and Core

  • Samuel

    Thanks a lot man!

    Very awesome tutorial

    much helpful

    Thanks!

  • http://www.facebook.com/johncpeden John Peden

    Doesn’t work for me, returns a PHP object but not the new update notification.

    • http://www.facebook.com/johncpeden John Peden

      Got it working, must have been the update intervals!

  • http://www.facebook.com/johncpeden John Peden

    Anyone had any luck making this work with Github or S3? I use Github for version control so it would be cool to push it to Github, have S3 sync the latest version and serve the updates from there.

  • Samuel

    Does anyone know the difference between ‘pre_site_transient_update_plugins’ , ‘site_transient_update_plugins’ and ‘transient_update_plugins’ hooks ? they are all valid wordpress hooks.

  • http://franceimage.com/ Fr. Image

    For me, only _site_transient_update_plugins makes sense in wordpress 3.5.1 core.
    However it is not a hook, it is the key of an option in database.

    I am detailing WordPress update notifications on http://franceimage.com/net/wordpress

  • Danielx64

    I’m just wondering if this will work on wordpress themes as well?

  • Pingback: Examining the WordPress Core, Plugin and Theme Update Procedure : WPMayor

  • Pingback: Examining the WordPress Core, Plugin and Theme Update Procedure

  • http://DanielJLewis.net/about Daniel J. Lewis

    Thanks for this! But I’m running into a problem where admin pages are including object(stdClass)#256 (3) and then an array dump that includes all of my plugins and their versions.

    This is on WordPress 3.5.1 and 3.6 beta.