Custom plugin update (WordPress)
I would like to be able to update plugins from an own repository: Most (if not all) plugins are open source, licensed under GPLv2 or GPLv3 and free as in free beer. I would prefer to buy just one license of a given plugin, and distribute it myself to other sites that I manage, than buying a separate plugin for each site.
Solution (2024.10
- Use
wp install --force
in combination with a local path to update a site - Use a site like plugins.example.com as a local repository for these plugins, on the same webserver as the sites to be updated (as I'll use the path, not the urls).
Overview - Custom plugin update
Various methods for custom plugin updates:
Custom update script
Around 2020/2021, I developed a script for updating plugins and themes, as part of a larger server-sided update script:
- Rename the existing plugin/theme folder, to e.g.:
pluginname-bk-yyyy.mm.dd
- Create a new folder with the original name of the plugin or theme, e.g.
pluginname
- Upload the updated plugin or theme to this new folder.
This script had some issues
- It didn't check if an update was actually needed, but updated it anyway whenever invoked
- Backups weren't deleted afterwards - I'm reluctant to automatically delete stuff
- Site wasn't put in maintenance mode during update - Asking for trouble
- All kinds of checks that the Plugin update procedure (WordPress) follows, are missing
- This is asking for trouble.
Recommendations
Don't do this. Use existing procedures instead, e.g., wp plugin install --force
or using the WordPress Update API.
Redirect the plugin update location
Plugins add code to instruct WordPress from where to update. This can be changed. An example that I haven't checked:
add_filter( 'pre_set_site_transient_update_plugins', 'check_for_custom_plugin_update' ); function check_for_custom_plugin_update( $transient ) { if ( empty( $transient->checked ) ) { return $transient; } // Replace this with your plugin slug and the URL where the plugin updates can be fetched $plugin_slug = 'my-custom-plugin'; $api_url = 'https://custom-plugin-server.com/api/plugin-update.json'; // Fetch the update information from the custom server $response = wp_remote_get( $api_url ); if ( is_wp_error( $response ) ) { return $transient; } $data = json_decode( wp_remote_retrieve_body( $response ) ); if ( version_compare( $data->new_version, $transient->checked[$plugin_slug], '>' ) ) { $transient->response[$plugin_slug] = (object) array( 'slug' => $plugin_slug, 'new_version' => $data->new_version, 'package' => $data->download_url, 'url' => $data->changelog_url, ); } return $transient; }
wp install --force - Custom URL
WP-CLI command wp plugin update
doesn't support custom source locations. However, using wp plugin install
with the flag --force
executed on a plugin that is already installed, effectively turns it into an update:
- Download the plugin zip from the custom URL
- Replace the existing plugin files with the new ones from the ZIP file
- Keep the plugin's existing settings and data (like with a typical update).
From a custom URL:
wp plugin install https://custom-server.com/path-to-plugin/plugin-name.zip --force
Somehow, I seem to be unable to get this to work. See wp plugin install for details.
wp install --force - Custom path
As before, but from a custom path, assuming the plugin is on the same server as the site:
wp plugin install /path/to/local/plugin-name.zip --force
BTW: The package has to be a zip file. See Wp plugin install (WP-CLI) for details.
Custom plugin update services
Some plugin developers use services like GitHub or private repositories to host their plugins and manage updates. In these cases, plugins can be configured to pull updates from sources like GitHub by integrating update mechanisms directly into the plugin code.
For example, you can use plugins like GitHub Updater to enable plugin updates from a GitHub repository:
- Install the GitHub Updater plugin (https://github.com/afragen/github-updater)
- Configure the plugin to point to the GitHub repository where the plugin is hosted
- The plugin will then check GitHub for updates and fetch new versions from there instead of the WordPress.org Plugin Repository
This can also work with other repositories like Bitbucket or custom servers, depending on how the update mechanism is implemented.
Custom plugins update APIs
If you're hosting your plugin updates on a custom server, you can build a custom API that responds to requests for plugin version information. Your plugin can check this API periodically (or when wp plugin update is run) to determine if an update is available and download the new version.
The steps involved include:
- Create a custom API on your server that responds with the latest plugin version, download link, and other relevant data
- Modify the plugin to check the custom API using wp_remote_get() and compare the version
- If an update is available, the API provides a ZIP file that can be downloaded and installed.
Package location
Where to store the source package?
GitHub
- Maybe the easiest approach: Store in the usual gitHub account?
- Conclusion: It turns out, that I have no 'manual interaction' with these packages at all: They are 100% handled by scripts, so it doesn't matter where it is located - That makes this option not so usefull
Pro
- Easy: I can prepare source packages at my workstation and push it to the various servers
- Flexible: No problem when using
wp plugin install
with a local path: Just enable this repository (or only parts of it) on each webserver - Universal: I already use GitHub for patching
Filterer.php
, so this fits in current procedures.
Con
- GitHub pushes are likely to get slower when such relative large packages are stored in it
Server directory
Place these packages at standard locations at webservers. E.g., /opt
Pro
- Works without GitHub
Con
- I don't like interacting with server storage through Nemo or
rsync
or so - Higher trashhold for doing it - Increasing the change that I won't do it at all
- I don't find such a location intuitive.
Plugins.example.com
- Store at an own URL - I didn't get this to work so far. Details: wp plugin install
- On the other hand: This 'repository site' is for me at the same server as the sites that need to be updated, so effectively it can still use a path, like
/var/www/plugins.example.com/dolly.zip
- Additional advantage: It's available from outside the server, if ever needed
- It's an intuitive location: I would remember to look here when I have to review all this six months or two years from now.
Dropbox
- It would be possible to generate these packages at a Dropbox account at a webserver, but it isn't very intuitive to me and possible technically challenging
- The repository actually doesn't have to be distributed: Currently (2024.10), all WordPress sites are at one giant server
- Not intuitive: Where in the Dropbox account to place these?
- Conclusion: Don't: Too complicated and contrived.
Packaging procedure
Now the interesting part: How to create these source packages? How to automate stuff as much as possible, while keeping procedures robust? The nice thing: I can start doing this manually and progressively update the script:
- Licenses will be used on one site:
example.nl
- This site will be the source for these custom packages - Custom packages can be created by simply zipping up the content of
wp-content/plugins/<plugin>/
- Names of these packages are straightforward: Just use the regular name/title of these packages. No need to include versioning codes
- Newly created custom packages, can overwrite the older ones in the GitHub account
- Have a script for this in the GitHub account, maybe called
wp_update_custom_plugin_packages
. In this script, enable/disable the functions that are needed to make specific packages - Ideally, this enabling/disabling wouldn't be needed, but that seems too much work to automate
Updating procedure
- In the script mentioned before, also include code for updating the other sites that use this plugin - Quite straightforward
Test: woocommerce-eu-vat-number
Context
- I have two licenses and have this plugin installed on nl_nl and fr_fr
- However, this plugin is probably installed on all similar websites, as these are basically clones of nl_nl
- Let's see if I can do custom plugin update on be_nl
- At nl_nl, version 2.9.3 is installed
- At be_nl, version 2.4.2 is installed, which is from 2022.02.22 - I can well imagine that nl_nl has been cloned to be_nl around that time
- At fr_fr, version 2.4.2 is installed. I'm surprised, as this is actually licensed.
Create woocommerce-eu-vat-number.zip
cd /var/www/nl_nl/wp-content/plugins zip_file_path_name="/home/jeroen/woocommerce-eu-vat-number.zip" source_directory="woocommerce-eu-vat-number" zip -r "${zip_file_path_name}" "${source_directory}"
Update fr_fr
This is the easy step, as fr_fr is actually licensed:
zip_file_path_name="/home/jeroen/woocommerce-eu-vat-number.zip" cd /var/www/fr_fr wp plugin list --name="woocommerce-eu-vat-number" wp plugin install --force "${zip_file_path_name}" wp plugin list --name="woocommerce-eu-vat-number"
Update be_nl
Now the real test: Does this procedure also work at a site for which there is no license for this plugin?
zip_file_path_name="/home/jeroen/woocommerce-eu-vat-number.zip" cd /var/www/be_nl wp plugin list --name="woocommerce-eu-vat-number" wp plugin install --force "${zip_file_path_name}" wp plugin list --name="woocommerce-eu-vat-number"
Update be_nl - Repeat?
What happens if you try to do this multiple times with the same package? Does --force
really forces a "meaningless" update? Or is it just skipped?