Automatic Updates For Private And Commercial Plugins

Last updated on June 26, 2015.

Since time immemorial, only plugins hosted in the official WordPress.org plugin directory have supported automatic updates. Now, I’ve written a PHP library that you can use to add automatic update capabilities to any plugin. Public, private and commercial plugins alike – all can now enjoy the benefits of automatic update notifications and one-click upgrades.

The custom update checker integrates closely with the upgrade system already built into WordPress, producing a seamless user experience. Observe :

An upgrade notice for a privately hosted plugin.

An upgrade notice for a privately hosted plugin.

The version information window with placeholder data

The version information window with placeholder data

Download

License

This library is released under the MIT License and is distributed free of charge. If you find it useful, consider making a donation.

Quick-start Guide

This section describes the quickest way to get automatic updates working for your plugin. Here’s what you’ll need to do: create a metadata file for your plugin, host it somewhere publicly accessible, and tell the update checker where to find it.

Lets start with the metadata. Copy the JSON code below into a new file and replace the placeholder values with your plugin’s info.

{
    "name" : "My Cool Plugin",
    "slug" : "my-cool-plugin",
    "download_url" : "https://example.com/plugins/my-cool-plugin.zip",
    "version" : "2.0",
    "author" : "John Smith",
    "sections" : {
        "description" : "Plugin description here. Basic HTML allowed."
    }
}

(This is the minimum amount of data required to make automatic updates work. In most cases, you will probably want to add a couple more fields. See the metadata docs for a full list.)

Most of the fields should be pretty self-explanatory, with one possible exception – the “slug”. WordPress expects all plugins that support automatic updates to have a unique textual identifier called the “slug”. Normally, slugs are assigned by the official plugin directory. For a private/commercial plugin that’s hosted elsewhere you’ll have to make something up. If unsure, just use the plugin’s file name without the “.php” extension (my-cool-plugin/my-cool-plugin.php becomes my-cool-plugin).

Upload the metadata file you just created to your web server. It doesn’t matter where exactly you put the file or how you name it. The important thing is for its URL to be accessible from wherever someone might install your plugin.

Next, copy the “plugin-update-checker” directory from the client library archive to your plugin’s directory. Then fire up your favourite code editor and add the following lines to the top of your plugin file:

require 'plugin-update-checker/plugin-update-checker.php';
$MyUpdateChecker = PucFactory::buildUpdateChecker(
    'https://example.com/path/to/metadata.json',
    __FILE__,
    'your-chosen-slug'
);

If you followed my advice and used the plugin’s file name as the slug, you can omit the third parameter of the PucFactory::buildUpdateChecker() call.

Tip: Sometimes you’ll run into a situation where another active plugin is also using this update checker. As a result, there could be several different versions of the library loaded at the same time. The above code snippet will always give you the latest available version. This can be a problem if your plugin expects an older version and is not API-compatible with the latest version.

To use a specific version of the update checker (e.g. the one included with your plugin), instantiate the PluginUpdateChecker_x_y class directly. Replace x and y with the major and minor version numbers:

//Use version 2.0 of the update checker.
require 'plugin-update-checker/plugin-update-checker.php';
$MyUpdateChecker = new PluginUpdateChecker_2_0 (
    'https://example.com/path/to/metadata.json',
    __FILE__,
    'your-chosen-slug'
);

And that, believe it or not, is it.

The PluginUpdateChecker class will handle the rest. It’ll check the metadata file every 12 hours and, if it discovers that a new version has been released, twiddle the right bits in the undocumented WP API to make it show up as a standard upgrade notification in the “Plugins” tab. Assuming you’ve provided a valid download_url, users will be able to install the update with a single click.

Tip: When creating the ZIP file for an update, put all plugin files inside a directory. The directory name should match the plugin slug. Do not put the files at the root of the ZIP archive – it can cause subtle bugs and errors when someone ties to install the update.

The rest of this post will be devoted to a more in-depth discussion of the update checker class and the metadata format.

The PluginUpdateChecker class

This class is the core of the update checker. It’s also the only part of the updater that you should need to deal with unless you decide to  extend the library yourself.

Class constructor

All configuration settings should be specified by passing them to the PucFactory::buildUpdateChecker() factory method, or directly to the PluginUpdateChecker constructor. Both takes the following parameters:

  • $metadataUrl – The full URL of the plugin’s metadata file.
  • $pluginFile – The path to the plugin’s file. In most cases you can simply use the __FILE__ constant here.
  • $slug – The plugin’s ‘slug’. If not specified, the filename part of $pluginFile (sans “.php”) will be used as the slug.
  • $checkPeriod – How often to check for updates (in hours). Defaults to checking every 12 hours. Set to zero to disable automatic update checks.
  • $optionName – Where to store book-keeping info about updates. Defaults to “external_updates-$slug”.

checkForUpdates()

Manually trigger an update check. This is especially useful when you’ve disabled automatic checks by setting $checkPeriod (above) to zero. This method takes no parameters and returns nothing.

addQueryArgFilter($callback)

Register a callback for filtering query arguments. Whenever the update checker needs to retrieve the metadata file, it will first run each filter callback and attach the query arguments that they return to the metadata URL. This lets you pass arbitrary data to the server hosting the metadata. For example, commercial plugins could use it to implement some kind of authorization scheme where only users that have the right “key” get automatic updates.

The callback function will be passed an associative array of query arguments and should return a modified array. By default, the update checker will add these arguments to the metadata URL:

  • installed_version – set to the currently installed version of the plugin.
  • checking_for_updates – set to 1 if checking for updates, absent otherwise (i.e. when loading data for the “Plugin Information” box).

This method takes one parameter – the callback function.

addHttpRequestArgFilter($callback)

Register a callback for filtering the various options passed to the built-in helper function wp_remote_get that the update checker uses to periodically download plugin metadata. The callback function should take one argument – an associative array of arguments – and return a modified array or arguments. See the WP documentation on wp_remote_get for details about what arguments are available and how they work.

This method takes one parameter – the callback function.

addResultFilter($callback)

Register a callback for filtering plugin info retrieved from the metadata URL.

The callback function should take two arguments. If the metadata was retrieved successfully, the first argument passed will be an instance of PluginInfo (see the source for a description of this class). Otherwise, it will be NULL. The second argument will be the corresponding return value of wp_remote_get (see WP docs for details). The callback function should return a new or modified instance of PluginInfo or NULL.

This method takes one parameter – the callback function.

Metadata format

The automatic update system uses a JSON-based file format to describe plugins.  Essentially, the entire file is one big JSON-encoded object (AKA hash-table or associative array). Each field – or array key – represents a piece of information about the latest version of the plugin. The full description of all available fields is here.

For the sake of simplicity, both general metadata and update-related information are stored in the same file. If this is undesirable, you can replace the plain JSON file with a script that checks for the presence of the the “checking_for_updates” query parameter and emits just the update-related fields if its set to “1”.

Notes

Your plugin must be active for updates to work. The update checker is just another piece of PHP code loaded and run by your plugin, and it won’t be run if the plugin is inactive.

One consequence of this that may not be immediately obvious is that on a multisite installation updates will only show up if the plugin is active on the main site. This is because update notifications usually appear in the network admin, and only plugins active on the main site are loaded in that case. The main site of a WordPress network is the one that was created first and has the path “/” in the Sites -> All Sites list.

Related posts :

496 Responses to “Automatic Updates For Private And Commercial Plugins”

  1. John says:

    This works great, so bravo!

    But…. I’m testing on a site which has one other plugin (wp-juicebox). My plugin generates an xml file and sends it as a parameter for a javascript in wp-juicebox. This adds an HTML5 gallery to the page. That’s the only way they interact.

    Each time I update my plugin, the gallery fails to display. If I try to call the javascript’s url directly, I get url 403 Forbidden. I can fix it by deleting the other plugin and uploading it again (accessing the script is then possible)

    Does this make sense? I think the update code may be setting more permissions that necessary, or not returning them to their original values. What do you think?

  2. Jānis Elsts says:

    Yes, that sounds like it could be a permissions issue.

    Unfortunately, I can’t really tell you much more than that. My update code doesn’t handle the actual update installation. It just detects when an update is available and tells WordPress where to download the new version (there’s a bit more to it, of course, but this is the basic idea). So when you click “update now”, it is the WordPress core that downloads and installs the update, not my library.

    If I had to guess, I’d say that your web server is probably running under a different user account than what PHP scripts run as, so it can’t access files created by PHP/WordPress. Alternatively, maybe some of the files have the wrong owner for some reason.

  3. John says:

    Thanks. It happens with both WP instances on this server. I’ll test on another server and report back if I find anything interesting.

  4. John says:

    Which PHP environment variables would I examine to test if your guess is right?

    I’ve now tested on other servers and I only hit the problem on one – and it happens in every instance of WP on that server. After the update, the other plugin’s File Permissions have changed to 664, preventing it from running, and switching them back to 705 resolves the problem.

  5. Jānis Elsts says:

    I don’t think it is possible to detect this kind of thing by looking at environment variables.

    You can usually check file ownership in your FTP client. The one I use displays file owner and group by default. You can see what user account your web server runs as by using the “top” command in the console – it displays the running processes and their owners. Finally, you might want to look into the umask setting which determines the default permissions that are set for newly created files.

  6. In order to really make solar panel but also then installing the same may
    seem like an awe-inspiring task.

  7. Thank a lot buddy you have saved my time i was searching from last two days it works

    10 out of 10

    A++++++

    gud job

    Thank you

  8. Hi Jānis,

    This is a great bit of code and I’ve been using it for a few months now.

    There’s just one issue I have, which is hard to track down. Some sites (i.e. some people who have installed my plugin) check in every few seconds. It looks like they are probably checking on every page load from their site. That creates a lot of load on my site (because it causes a WordPress load on my site every time it does on theirs, so I get the combined load of all the sites with this problem! e.g. One site that I can see doing this causes me an extra 20,000 page loads a day).

    Is this a known issue? (My version of the updater is from February). Any idea how I might debug it? Using data that’s sent with the update request, I can see that the site mentioned above is using WordPress 3.5.1.

    Many thanks,
    David

  9. Jānis Elsts says:

    Do any of those sites have BackupBuddy or some other iThemes plugin(s) installed? A while ago I noticed a similar problem with one of my own plugins, and after a lot of digging I tracked it down to a bug in BackupBuddy that prevented the update checker from creating a custom cron schedule. As a result, it would send an update request on every other page load.

    I’ve since implemented a work-around (see this commit) that fixes the issue. Make sure you have the latest version of the library.

    Unfortunately, some users never seem to update their plugins, so in practice my server still gets 10 000+ redundant requests/day from a bunch of sites that are running an older, unpatched version of the plugin. I dealt with that by redesigning my update server script so that it could be used without loading WordPress, which greatly improved performance and solved the server load problem.

  10. Hi Jānis,

    Thank you! I haven’t had access to the sites causing the problem, but having read the code, I had guessed that this was the most likely cause – i.e. that another plugin was over-writing the cron schedules. It is encouraging to hear that you had found that problem. I had emailed one of the siteowners with a fix that over-rode the new ’24-hour’ schedule (that I was using) with WP’s built-in ‘daily’ schedule to avoid the issue, in the hope that that might fix it. Your fix is better (as mine was local, only worked for people on 24 hours) – however your fix will still trigger the problem if someone else uses a higher priority. How about both fixes? (i.e. you should *also* use the WP-built in schedules if someone happens to choose 24 or 12 hours (daily/twicedaily))???

    David

  11. P.S. In my case, 1 user with a very popular website accounts for over 50% of update checks, so I just need to hope that he updates!!

  12. Jānis Elsts says:

    Using the default schedules when possible seems like a good idea. Based on my reading of WP source code plugins cannot override the defaults, so they should be safe to use.

    I could modify installHooks() like this:

    if ( $this->checkPeriod > 0 ){
    	
    	//Trigger the check via Cron.
    	//Try to use one of the default schedules if possible as it's less likely to conflict
    	//with other plugins and their custom schedules.
    	$defaultSchedules = array(
    		1  => 'hourly',
    		12 => 'twicedaily',
    		24 => 'daily',
    	);
    	if ( array_key_exists($this->checkPeriod, $defaultSchedules) ) {
    		$scheduleName = $defaultSchedules[$this->checkPeriod];
    	} else {
    		//Use a custom cron schedule.
    		$scheduleName = 'every' . $this->checkPeriod . 'hours';
    		add_filter('cron_schedules', array($this, '_addCustomSchedule'));
    	}
    
    	if ( !wp_next_scheduled($this->cronHook) && !defined('WP_INSTALLING') ) {
    		wp_schedule_event(time(), $scheduleName, $this->cronHook);
    	}
    	add_action($this->cronHook, array($this, 'checkForUpdates'));
    	
    	/* ... */
    }
    

    What do you think?

  13. Hi Jānis,

    That looks good to me.

    I guessed this was the problem because on another plugin I had someone tell me that only certain schedules worked, and others didn’t – the ones that worked were the built-in ones. So defaulting to using those if possible seems the lowest-risk option.

    David

  14. Jānis Elsts says:

    Okay, I’ve committed the change to GitHub.

  15. curriki.org says:

    It’s an remarkable post in favor of all the internet people; they will obtain advantage from it I am sure.

  16. Alex says:

    Hi Jānis,

    Thanks for this invaluable library and your effort.. it is awesome!

    Have one suggestion and one question.

    First off a question:
    Updates are handled only in manner 1, 4, 12, hours defined in your PluginUpdateChecker class?
    Wordpress itself won’t notice new versions, will it?
    And also checkForUpdates() only triggered in the inlined plugin link “Check for updates”?
    I’ve tried to click on wordpress button in “Updates” -> “Check Again”, but that has resulted no effect..

    Secondly some perhaps suggestion..
    Would be it more convenient if we could to create a special Plugin-Updater Plugin instead of placing each time into new plugin directory “plugin-updater folder”?
    Into that plugin we could specify what plugins do we need to update, and also define checkPeriod.
    By this I hope we could centralise all update control.
    And also in case of new updates from your part (for plugin-updates core) we will just update one plugin all across websites.. rather than each plugin directory in doezens websites..
    Or I missed something?

    Thank you in advance

    Looking forward to hearing form you

    Regards,
    Alex

  17. Jānis Elsts says:

    That’s more than one question 🙂 I’ll try to answer the questions in order:

    Updates are handled only in manner 1, 4, 12, hours defined in your PluginUpdateChecker class?

    You can also choose a different update check period, like 7, 48, or 1234 hours. It doesn’t have to be one of the three defaults. To change the check period, pass it in as the fourth argument to the PluginUpdateChecker constructor.

    WordPress itself won’t notice new versions, will it?

    No, it won’t. WordPress does not support third-party update APIs, which is why a library like this exists in the first place.

    And also checkForUpdates() only triggered in the inlined plugin link “Check for updates”? I’ve tried to click on wordpress button in “Updates” -> “Check Again”, but that has resulted no effect..

    Yes. At the moment, the library checks for updates independently of WordPress. I implemented it this way to avoid accidentally breaking other plugins. If it checked for updates at the same time as WordPress does, then a serious bug or time-out in PluginUpdateChecker could also crash the built-in update checker and break updates for other installed plugins.

    Of course, this also works the other way around – if another plugin uses a buggy update checker that crashes occasionally, trying to run your own update check in the same request can lead to problems.

    Would be it more convenient if we could to create a special Plugin-Updater Plugin instead of placing each time into new plugin directory “plugin-updater folder”?
    Into that plugin we could specify what plugins do we need to update, and also define checkPeriod.

    Yes, this approach would be more convenient for developers and maybe even some users. However, it also has some problems. Just off the top of my head:

    • How do you get users to install this updater plugin?
    • Do you list it as a requirement? Many people don’t read documentation/requirements and will end up complaining that your plugin “doesn’t work”.
    • Do you pop up a notification after they’ve already installed your plugin? This requires more programming on your part, and could annoy users (“Why do I need to install another plugin just to use this one? My site is slow already!”).
    • Do you bundle it with your plugin, or download and install it automatically? This requires more code and introduces additional failure points.
    • What if someone doesn’t have the updater installed and doesn’t want to install it? Do you just say “that’s it, you can’t use my plugin”, or do you let them use it anyway (and thus be forced to support every old version of your plugin ever released, because some users will never update)?
    • No matter how you do this, installing two plugins is still harder and more time-consuming than installing one. This means people will be less willing to use your plugin.
  18. M Teguh A Suandi says:

    Is it possible to implement the autoupdater into the wordpress themes?

  19. Jānis Elsts says:

    For themes, use this library instead.

  20. M Teguh A Suandi says:

    Ouch, i didn’t see that.. Thank you.

    M Teguh

Leave a Reply