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 :
Download
- Client library (requires WP 3.2 or later)
- Example plugin
- Example metadata file
- GitHub repository
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 :
I should say with my above comment, this only happens if an update is available.
Is that on a normal site or a Multisite network? Unfortunately, I can’t seem to reproduce the notice on any of my non-Multisite test sites.
Hi, thanks for responding.
After submitting my question I realized I needed to update all versions of the UpdateChecker as I was still on 1.4.
Thanks for a great script.
Cheers
I tried it on localhost and it is returning this kind of error:
Warning: The URL http://xxxxx.xxxxx.com/update/v/ult/metadata.json?checking_for_updates=1&installed_version=0.1 does not point to a valid plugin metadata file. wp_remote_get() returned an unexpected result. in D:\xampp\htdocs\wordpress\wp-content\plugins\wpplugin\plugin-update-checker\plugin-update-checker.php on line 238
What would be the problem here?
Thanks in advance…
That’s one of those “this should never happen” errors. The update checker uses the built-in WordPress HTTP API to retrieve update information. Normally the API returns either a HTTP response (status code, headers, etc) or an error object. In your case, it returned something else. That’s supposed to be impossible.
Can you post the full URL of your metadata file? I’ll try it on my own local site and see if I can reproduce the error. If you don’t want to make the URL public, you can email it to me: whiteshadow@w-shadow.com
Also, it might be a good idea to add additional logging and debug output to the
requestInfo
function in plugin-update-checker.php so that you can see whatwp_remote_get
actually returns. My guess would be that there’s either some kind of unusual network problem, or one of your other plugins is changing HTTP API results in an unexpected way.This is great… thanks..
I am experiencing an issue with the actual install. It wont download and install the new version. I m hosting the .zip file on AWS (amazon services) S3.
Please advise
thx
What specifically is the issue? For example, is there an error message of some kind, or does the install time out, or does it say that the update was installed but the actual plugin was unchanged, or something else? Does the download URL work if you try it yourself?
Hi…
It says the update was installed but the actual plugin was unchanged… there are no errors that I can see… I looked at the logs, but nothing.
If I try the URL directly on the browser, the .zip file is downloaded. The original url was https, but I have tried http. I am hosting the zip file on AWS S3.
Thanks
l.
This may sound obvious, but are you sure that the version that the URL points to is actually different from the installed version? I’ve run into similar problems before where it turned out I had simply forgotten to update the version number or I uploaded the wrong file.
I will double check, but I am pretty sure that I updated the version. I will re-upload to make sure S3 is not caching the older version. Any other options to verify in case?
The file in AWS S3 is the new version. Is there a way that I can debug that process? When I click to update, it shows the spinning icon, then it says Updated in green but then I still see the older version of the plugin installed.
Well, you could make some small change to the installed version right before clicking “update”. For example, add a short comment to the main plugin PHP file, or delete an unimportant image. Then update the plugin and check if your change is still there. If it’s gone, that would mean that the update did actually download and install a version of the plugin from somewhere – even if it wasn’t the version you expected.
[…] also have plugins using the plugin update checker via Janis / w-shadow because they are not hosted on WordPress.org … so how do other plugin developers track […]
Hi Jānis
Awesome code, thank you so much. However, i seem to be stuck here and was hoping you could give me some direction. I have everything up and running. The plugin updates without any hassle BUT, the version number doesn’t update AND, the plugin (although updated) still has the “There is a new version of xxxxx ” available. Any idea whats happening. Thank you in advance.
Regards
Here a few things to check:
– Does the download_url point to the right version? Download the update manually and look at the “Version” header.
– Does updating the plugin actually modify its files? As mentioned in an earlier comment, you can test that by making a small change (e.g. adding a comment) to the plugin file just before updating. Then check if the change is undone by the update.
– Does the update get installed in the correct directory? Maybe it creates a new directory instead and installs the update there, leaving the existing version unchanged? (This can happen if your ZIP file is structured incorrectly.)
– Are you using the latest version of the update checker? Try the master branch on GitHub.
Hi Jānis
Thanks for your speedy reply.
Let me double check and get back to you. The modification, yes! The files get changed when updating and it does update to the correct dir. I will check the rest real quick.
Regards
Hi Jānis
Quick update … ends up i did forget to set the correct version number in the update header.
Thanks so much for your awesome work. Donation will be made.
CHeers
Hi again,
Last question ..
added the .png images to the metadata. all information displays in the popup for update BUT the images, not.
Thanks
Can you reproduce that with the example plugin (the download link is in the post above)? It uses this example metadata. Check how it specifies image settings and compare that with your own JSON.
Here is the error I keep getting with a localhost test plugin using Namespacing:
Fatal error: Class ‘PluginUpdateChecker_2_0’ not found in /Applications/MAMP/www/support/wp-content/plugins/c960d-ledger/plugin-updates/plugin-update-checker.php on line 1137
I’ve made the obvious verification, the PluginUpdateChecker_2_0 class is in there. I’ve also added NameSpace to all classes in the plugin download. Any ideas? I read through a couple hundred of the replies to see if someone had anything similar and couldn’t find anything.