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. Sagar says:

    When i click on “View version 2.0 details. ”
    i am getting error “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.”

    how to fix it

    Thank you in advance

    Looking forward to hearing form you

    Regards,
    Sagar

  2. Jānis Elsts says:

    I have never seen that particular error. Make sure you’re passing the correct slug to the update checker constructor and check your PHP error log for recent error messages.

  3. alan says:

    hey, can you confirm this line of code
    require ‘plugin-updates/plugin-update-checker.php’;
    $MyUpdateChecker = new PluginUpdateChecker(
    ‘http://example.com/path/to/metadata.json’,
    __FILE__,
    ‘your-chosen-slug’
    );

    You say add to top of your plugin file, which file is this, is it a new file inside the plugin, or the index.php etc. sorry to sound stupid.

    thanks

  4. Jānis Elsts says:

    The best place to put depends on how your plugin is structured. If you’re not sure/don’t care, just put it in the file that contains the plugin header.

  5. alan says:

    Ok thanks fro your reply , so can i put this code before the plugin header

  6. Jānis Elsts says:

    Actually, I’d recommend putting it after the header. Normally the header should be the first thing in the file.

  7. alan says:

    yea was thinking that to. thanks for your help

  8. Eric Schwarz says:

    Hey I just switched to your updater and got it to work but I have multsite and only shows the update if the main blog is running the theme. Any work arounds for this?

  9. charlie says:

    Got a bit of a thread going on wordpress.org (see http://wordpress.org/ideas/topic/plugin-update-alert-for-installed-plugins-not-sourced-from-wordpressorg?replies=5#post-25698) that refers to your plugin.

    Is it possible to alert wordpress to the latest version of a plugin, without being required to specify the zip file for that new version? The use case being that in some instances it may not be possible (or desirable) to provide the updated zip file. Some marketplaces for example may require that updates are downloaded from their system not within wordpress.

    That being the case, it would still be useful to notify a user of an updated plugin, even if they have to manually upgrade the plugin by uploading the zip file.

    If this is not currently possible is that a limitation of your code or a limitation of wordpress itself?

    thanks a lot

  10. Jānis Elsts says:

    Just leave the “download_url” field empty. An update notification will still show up, but without the “update now” link.

  11. Carles says:

    Hello. I use your script in my commercial plugin, and works really good, thanks!!!

    However, I’ve a customer who can’t update. When he click on “check for updates” get an “This plugin is up to date.” message.

    In another place in my plugin, I read remotely a file (from my webserver) into the admin side, using: wp_remote_get() function, to announce other featured products. It fails also, I think the PHP config have restrictions to remote webservers…

    But… the WP can update his repository plugins, and install new ones, I tested it…

    Can do you help me?
    Thanks,
    Carles.

  12. Jānis Elsts says:

    Try installing Debug Bar. The update checker can use it to display a bunch of useful debugging information.

    Once it’s installed, click the “Debug” link in the WP toolbar (top left) and then go to the “PUC (your-plugin-slug)” tab. See if there’s anything wrong or suspicious there. Also, click “Request info” and see if correctly displays your plugin information.

    It could be that the customer’s server is be blocking outgoing HTTP requests to everything except a small whitelist of domains. Presumably, wordpress.org would be on that list, which would allow WP updates to work. If that is the case, you probably won’t be able to get the update checker working unless you can get your site whitelisted as well.

  13. Carles says:

    Hi, Yanis, thank you very much to appointme in the right way: the webserver block the connection to my website, but not the wordpress.org connection… should be a “white list”…

  14. Carles says:

    Hello Janis, after digging I found a way: an https communication. (My bank forced me time ago to get the https certificate to allow credit card gateway).

    I think will be great if your script allow a 2nd url fallback (https in my case, but can be a redundant webserver for others)…

    Or… there is a way to catch a forbidden error when look for updates and retry the update with the 2nd URL?

    Thank you very much,
    Carles.

  15. Jānis Elsts says:

    Sorry, it’s not possible to have a fallback URL. However, why not just use the HTTPS URL all the time instead of as a fallback? Would it increase your server load or something?

  16. Tino Hartung says:

    Hi,

    this is a great class and I got it to work just fine. However installed this on two different plugins and loaded both into the same WP.

    Everytime I’m now checking for updates I get the ‘This plugin is up to date’ message or the ‘A new updates is available’ message on top of the page twice!

    The update process still works fine but it shows a few warnings that only disappear once I reload the page.

    I tried to rename the PluginUpdateChecker class on one of the two plugins but that did not make a difference.

    Any idea of what I’m doing wrong and what’s going on here?

  17. Jānis Elsts says:

    Make sure each plugin has a unique slug (it’s the third argument to the update checker constructor). In most cases, the slug should be the same as the plugin folder name.

  18. Carles says:

    Turn always https updates: I’m affraid it stop working some other webserver config. The hardest side of plugin development is the dubt in every change: it will work for everyone? Or will solve an issue to 1% and break another 2% ???

    I will do a pre-test on every update: first try to connect through HTTP, and then through HTTPS as fallback and saved in the preferences…

    Thank you very much Janis, your work & support is amazing! So many time ago I was donate to you, and I’m very happy to did it.

    Kind regards,
    Carles.

  19. Bill Phelps says:

    Hey – just what I was looking for. You’ve saved me days of research and coding. Small donation on its way : )

    I’m having the same problem as @Tino Hartung – multiple notifications (identical) at the top of the page, corresponding to the number of plugins that use the checker. Each plugin has its own unique slug (3rd parameter) that matches the one in the .json file.

    I had all the .jsons and all the .zips in the same directory on the server, so then tried separating them into separate subdirectories – but no change in behaviour.

    It would be good to sort this issue – but apart from this, the class works brilliantly.

    Many many thanks

    Bill

Leave a Reply