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. […] Automatic Updates For Private And Commercial Plugins GitHub […]

  2. Terry Hale says:

    Jānis, I can’t thank you enough for all the hard work you’ve put into this! One of the most amazing WordPress tools I’ve ever come across. <3

    I am having a real problem after 3 or 4 hours of trying to get the content of the readme.txt file within the plugin to be recognized when using the server action. If I use a link to the .json file instead, it seems to work just fine.

    Here is the code I'm using, if you have any ideas.. As I said, if I switch from using the update server action (comment that out) and uncomment using the .json, it works.

    require 'plugin-update-checker/plugin-update-checker.php';

    $MyUpdateChecker = Puc_v4_Factory::buildUpdateChecker(
    // 'http://dev.mizagorn.com/themes-plugins-updater/packages/image-intense.json&#039;,
    'http://dev.mizagorn.com/themes-plugins-updater/?action=get_metadata&slug=image-intense&#039;,
    __FILE__,
    'image-intense'
    );

  3. Jānis Elsts says:

    It looks like the update server returns an empty response. This could mean that there was a PHP error that caused it to crash. Is there anything relevant in your PHP error logs?

  4. Terry Hale says:

    Thanks so much for the quick response! I am looking into how to log PHP errors on my server. I really appreciate what you’re doing here.

  5. Shapon says:

    Do it work with NGINX SERVER?
    It’s working well in Apache server.

    How you any solution to work with NGINX server?

  6. Jānis Elsts says:

    I haven’t tested it with NGINX, but I believe that it would work fine. The update checker doesn’t use any Apache-specific modules or extensions.

  7. Shapon says:

    Thanks for your quick response.
    I am using custom NGINX server. I am trig from many days to work it. But it does not takes any response from my NGNIX server. but same code and file works well in Apache Server.

    Can you tell mew what modules or extensions need to enable musty in my NGNIX server?

  8. Jānis Elsts says:

    Are you hosting the update information on the server or the update checker itself? Also, are you using a static .json file, the wp-update-server script, or some other way to generate update metadata? If you’re using NGINX to store/output update metadata, have you already verified that the metadata URL works if you access it in the browser?

    Just to clarify, when I said it should work fine I was referring to the plugin-update-checker library, not any other scripts.

    Can you tell mew what modules or extensions need to enable musty in my NGNIX server?

    No idea. Sorry.

  9. Shapon says:

    I am not using static .json file. I am creating it dynamically by php code.
    [ echo json_encode($data, true);]
    I can show a valid json by url which will dynamically created and linked to plugin.
    It works well in apache server.
    http://shaponpal.com/winnrepos/plugins/index

    Have this any problem?

    Thank you very much for your helping.

  10. Jānis Elsts says:

    I see. Are you sure your code that outputs the JSON is correct? Aside from being valid JSON, it also needs to have the required fields/keys. Can you post a specific JSON URL so that I can check the data? If you don’t want to make it public, you can use contact form to contact me.

  11. HG says:

    Is this all secure, as far as escaping/sanitizing/nonces etc. goes? I’m not sure how I’d go about securing it myself, although I imagine I could figure it out if I needed to… but if it’s already coded with best security practices then I won’t worry about it. 😉

  12. Jānis Elsts says:

    Secure in what way? The update checker doesn’t take any user input, so escaping and sanitizing don’t seem relevant.

    As for nonces, the only obvious places where that would apply are: 1) installing an update and 2) manually checking for updates. WordPress core handles #1, so I would assume it’s reasonably secure. As for manual checks, the library does use nonces for that, and it also checks user capabilities to ensure only admins can check for updates.

    Or are you looking for a different kind of security, like ensuring that only users who have purchased a license can download updates? That’s outside the scope of this library. You would have to implement it yourself.

  13. Jānis, thank you very much for your hard work, i was looking for it and your solution fits perfectly for my needs.

    I’m having a little trouble for use it, i’ve created on my website a subdomain called updater.mysite.com/name-of-the-plugin/info.js (https://updater.seventhemestudio.com/analyticator-fbpixels/info.js) everything looks fine, i’ve upload the plugin-update-checker folder into my plugin’s folder.

    And copy the code from your website, as you can see bellow:

    require_once ‘inc/plugin-update-checker/plugin-update-checker.php’;
    $MyUpdateChecker = PucFactory::buildUpdateChecker (
    ‘https://updater.seventhemestudio.com/analyticator-fbpixels/info.json’,
    __FILE__,
    ‘analyticator-fbpixels’
    );

    On my installed plugins page i see a new link besides my “Visit Plugin Site” called “Chekc for new updates” but when i click there, it says that is up to date, but on my info.json i wrote version 2.0.0 and on my plugin it is 1.0.0

    Do you have an idea of what can be wrong?

    Thank you very much!

  14. I saw i had an SSL issue on https://updater.seventhemestudio.com/ so i’ve changed the URL to https://seventhemestudio.com/updater/analyticator-fbpixels/info.json but it stills not working :/

  15. Jānis Elsts says:

    Install Debug Bar, open the “PUC (analyticator-fbpixels)” debug panel and click the “Request Info” button. Does it show the update details that you would expect?

  16. Thanks for your quickly answer 🙂

    I’ve tryied it but its just a blank page: https://s2.postimg.org/68lztqz0p/debug.jpg

    It has some issues about the debug plugin which is using a deprecated __construct method.

    Theres nothing on the PUC (analyticator-fbpixels) panel

  17. Jānis Elsts says:

    It sounds like you might have an old version of PUC. Try downloading the latest release from GitHub:
    https://github.com/YahnisElsts/plugin-update-checker

  18. Yeeah, i was using an old version, now i’m using the new version, it stills says that my plugin is up to date, but now the desbug page has something: https://s13.postimg.org/4wedya247/debug.jpg

    Sorry boring you about that 🙂

  19. I’ve modified somethings and now i’m getting this error:

    Warning: The URL https://seventhemestudio.com/updater/analyticator-fbpixels/info.json?installed_version=1.0.0&php=5.5.38&locale=en_US&checking_for_updates=1 does not point to a valid metadata file. HTTP response code is 404 (expected: 200) in /home/seventhe/public_html/wp-content/plugins/analyticator-fbpixel/inc/plugin-update-checker/Puc/v4p1/UpdateChecker.php on line 297

    Warning: Cannot modify header information – headers already sent by (output started at /home/seventhe/public_html/wp-content/plugins/analyticator-fbpixel/inc/plugin-update-checker/Puc/v4p1/UpdateChecker.php:297) in /home/seventhe/public_html/wp-includes/pluggable.php on line 1195

    But the json file is ok if you teste the URL: https://seventhemestudio.com/updater/analyticator-fbpixels/info.json?installed_version=1.0.0&php=5.5.38&locale=en_US&checking_for_updates=1

  20. Jānis Elsts says:

    Is there anything on the site that would prevent it from making outgoing HTTP requests? Maybe a firewall?

Leave a Reply