Custom WordPress Plugin & Theme directory with native updater

For our Agency we needed a solution to update our custom developed WordPress Themes and Plugins on client sites. Because the themes were custom build for the client we were not able to submit them to the official WordPress Theme & Plugin directories und therefore had no native update path besides replacing the files on the server by hand.

Another problem was, that we needed to create some sort of licensing system. So only the client with the key would be able to use the software on their site or possibly sites.

And of course the process of releasing the updates should be made as frictionless as possible, so it would actually be used.

Coming up with a solution

licensing

To solve the first problem of having to manage licensing we found a WordPress based solution called Software License Manager Plugin for WordPress. This tool allowed us to create unique licenses that we could attach to products and even domains.

And the most important part was, that we were able to query the licenses and check their status through the Rest-API.

macbook frame

custom registry

The custom registry was something that we wanted to build from the ground up to fit our needs. So we decided to build a node application that interacted with the filesystem and served a Rest-API through using express.

GET endpoints

package/:name
{
	"name": "name",
	"description": "description",
	"latestVersion": "1.0.0",
	"tested": "5.2.1",
	"requires": "5.0"
}
package/:name/:version
{
	"name": "name",
	"version": "1.0.0",
	"releaseTitle": "Release Title",
	"releaseNotes": "Some release notes",
	"releaseNotesHtml": "<p>Some release notes</p>",
	"tested": "5.2.1",
	"requires": "5.0",
	"downloadUrl": "https://base-url/package/:name/:version/download"
}

The releaseNotes are coming directly from the GitHub release notes and are written in Markdown. In order for them to de displayed correctly on the web, there is the releaseNotesHtml parameter that automatically generates the html from the markdown.

package/:name/:version/notes
<p>Some release notes</p>
package/:name/:version/download

Returns the Zip file.

POST endpoints

hooking into the Core Update functionality

to use the standard WordPress Update page and functionality you can hook into the pre_set_site_transient_update_plugins and pre_set_site_transient_update_themes hooks. Inside of our function we then only need to fetch the information about the latest release of the given Plugin or Theme and compare the version number that’s installed with the one on the server.

If the version on the server is newer the new informations will be returned from the function with the url to download the new package.

In our case because we also needed to have a way to enter the license, we also created an admin page where you could manage your license key. And because we didn’t want to include all this code in all our Plugins and Themes, we decided to put all that functionality into a mu-plugin that gets installed on every site.

release tooling

This was the most fun part of the entire project. We use GitHub for everything, including Plugins and Themes of course. And with the release of GitHub Actions we build everything on top of that. So whenever you create, update or delete a release in the GitHub repository the custom Action is triggered. This action takes the content of the repository, strips out all the build tooling and development files and zips the entire folder. This folder and all the metadata is then send to the API, where the new release will be created, updated or deleted.

macbook frame

So there is no additional step for a developer on our team, besides crating the release in the repository on github.com.

Update:

This was build using the first beta version of Github Actions and they just recently released a new version of the beta that changed the process of registering these actions completely. So the action itself will need to be converted from using the .workflow file to the new .yml syntax. But with all the time it has saved us over the last months it has still been worth it to go all in so early.