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. White Shadow says:

    I actually had a “Donate” button up there for years. Depending on your level of cynicism, you would be quite surprised – or not – about how little use it got. So I got rid of it a few months ago.

    (That said, I do regularly receive some donations from the link I have in one of my plugins. Of course, that plugin has something like ~50 000 active users, whereas this blog only has ~380 subscribers.)

  2. Dalton says:

    I think your “free for personal use, paid for commercial themes” model sounds great. I’m sure a lot of small developers would appreciate it!

    Dalton

  3. White Shadow says:

    All right then, I’ll add it to my list of project ideas/to-do list.

    (Thanks for the donation :))

  4. link(salt) says:

    Wow .. after a long day and many tries, this is the first plugin that actually works updating self-hosted plugins.
    I got it to update the files, but for the life of me I cannot seem to get the popup window to populate with metadata.

    I have tried all kinds of versions of the .json file and all data up to “sections” goes through correctly. But when I click on ‘View version XYZ information’ I just get an empty box. No ratings , no description nothing just a blue FYI box (also empty, CSS?).

    If I could get that box populated too I’d rate this plugin into high heaven !

    in short, the info below does not get parsed.

    “sections” : {
    “description” : “(Required) This section will be opened by default when the user clicks on ‘View version XYZ information’. Basic HTML can be used in all sections.”,
    “installation” : “(Recommended) Installation instructions.”,
    “changelog” : “(Recommended) Changelog.”,
    “custom_section” : “This is a custom section labeled ‘Custom Section’.”
    },

    “rating” : 90,
    “num_ratings” : 123,
    “downloaded” : 1234

  5. White Shadow says:

    If I understand you correctly, even fields listed before “sections” – like the version number and author name – don’t show up in the popup window. This makes me think the problem is not related to parsing.

    Most likely, something goes wrong inside PluginUpdateChecker::injectInfo (plugin-update-checker.php, line 234). This is the callback that WordPress should use to get the data to populate the popup window with. If I was in your place, I would add some echo and var_dump calls there and try to figure out where and how it fails.

  6. link(salt) says:

    after some echo’s and print_r’s I patched the problem as follows (blocking out $relevant check). Have not dug deep enough to see what it does, but commenting it out does the trick and popup is fully populated now.

    function injectInfo($result, $action = null, $args = null){
    $relevant = ($action == ‘plugin_information’) && isset($args->slug) && ($args->slug == $this->slug);
    /*
    if ( !$relevant ){
    return $result;
    }
    */
    $pluginInfo = $this->requestInfo();
    if ($pluginInfo){
    return $pluginInfo->toWpFormat();
    }

    return $result;
    }

  7. White Shadow says:

    That, however, will make the update checker override the plugin API for all plugins, including those hosted on wordpress.org. So it’ll work fine for your plugin, but break lots of other plugin-related stuff, including the ability to find and install plugins from the official plugin directory.

    What you really need to find out is why $relevant ends up as FALSE there. Is $args->slug not set, or does it not match the slug you set when you initialize the updater, if any?

  8. Top Quark says:

    Hi White Shadow,

    I’m a developer in the process of launching my own plugin site and was searching far and wide for a self hosted plugins solution. I found this page and liked the code that you made.

    I’ve taken your plugin-updater class and incorporated it into a new plugin that allows anyone to self-host their plugins. All you have to do is upload a .ZIP of a valid plugin (including readme.txt). It allows versioning, shortcodes, counting downloads and will automagically generate the JSON to send back to the remote server on an update request.

    It just got approved into the repository this morning. I hope people find it useful and a huge thanks to you for releasing your code to the community.

    The Self Hosted Plugins plugin can be downloaded at http://wordpress.org/extend/plugins/self-hosted-plugins/

    Cheers
    Top Quark
    @topquarky

    p.s. I see you just released a theme-update-checker class. It would be great to create a sibling self-hosted-themes plugin!!

  9. […] this link for plugins and this one for themes. Related Articles:WordPress Updates to Version 3.0.2 – Do It! – […]

  10. tonli.eu says:

    Oh the irony ….

    While it is fun to make WordPress plugins, creating a plugin to host plugins away from wordpress.org seems ….. not sure ….. 🙂

    We picked up on this idea because wordpress.org censored some plugins, removed some plugins at random without explanation, has strict license policies, and has a difficult SVN system to maintain their plugins.

    For all those people affected we created http://tonli.eu which is based entirely upon the code by w-shadow.com.
    We assumed that tonli.eu was what the maker had in mind. A simple one-click upload of your plugin, and no difficult side-tracking.

    Either way, we thought we’d let everyone know that you can see the code above put into action on the alternative plugin hosting site.
    At the time of this writing it is fully functional / operational, but bells and whistles are still to be added.

  11. Top Quark says:

    Third-party plugin hosting opportunities are good options, but it’s fun to host plugins on your own site, no? Especially for developers who want to charge for their babies.

    My plugin mimics the look and feel of wordpress.org/extend/plugins, I think giving end users a greater sense of confidence in the plugin.

    I certainly hope WordPress.org doesn’t take my plugin down, but if they do, I’m set up to host it off of my own site – updates and all. I’m also working on the bells & whistles to be able to allow authenticated updates and restricted downloads – allowing developers to charge for their work.

    To see the Self Hosted Plugins plugin in action, you can visit my own site – http://topquark.com/extend/plugins/formidable-plus/

    Cheers
    Top Quark

  12. Steve F says:

    @link(salt)

    I had the same problem that you had. To test my code I changed

    if ( !$relevant ){
    return $result;
    }

    to

    if ( !$relevant ){
    var_dump($this->slug);
    return $result;
    }

    and discovered that the slug name i entered on the update server was not the same as the local one. It did not matter that they were different for the version check, only for the info download. I have no idea as yet why that might be.

    Also @White Shadow, I noticed you use site_transient_update_plugins as your hook. Some people use pre_set_site_transient_update_plugins. Whats the difference between the two.

    Oh and its a fantastic plugin. 🙂 Thanks.
    Steve

  13. […] That feature is actually provided by a third-party, the background information is available here: http://w-shadow.com/blog/2010/09/02/automatic-updates-for-any-plugin/ It doesn't sound like it's having any negative impact on the actual function of the plugin, though? […]

  14. Patrik R. says:

    Hey, i’ve downloaded and added it to my plugin folder..

    Now the version number of my plugin is 1.0 (its in the header.. you know) and i set in the metadata the version to 2.0..

    So i’ve changed it to 1hour.. but it didn’t show any things to make an update.. or does this happen automatically???

    I just used the code you gave in the example..

    Please reply back so i can know if this still works or if its outdated…

    Thanks!

  15. White Shadow says:

    It should still work, and the update notification should show up – it doesn’t install the update automatically. Make sure your metadata file is correctly formatted. You can paste it in JSONLint to check if the syntax is right.

    Also, if you want to force an update check (e.g. for testing), use the checkForUpdates() function. See the post itself for details.

  16. Meglio says:

    There more simpler and free plugin that does the same:
    https://github.com/meglio/wp-upgrademe

  17. Patrik R. says:

    Holy crap it’s working!

    I think i just didn’t wait long enough… i had set it to 1 hour.. but it’s working now.

    Thank god & the creator of course 😉 !!!

  18. TomJ says:

    Ive been seeing issues with this and WP 3.2.1 its not showing theres an update or giving the notice at the top of the dashboard. But when you go to Dashboard > Updates its clearly there.

    Anyone else experienced this?

  19. mindshare says:

    Is it possible to call the actual Update function itself from within PHP (without the user clicking the update link in the Dashboard)?

Leave a Reply