Library / SDK
YahnisElsts/plugin-update-checker avatar
YahnisElsts/plugin-update-checker

Plugin Update Checker: Self-Hosted WordPress Updates Without WordPress.org

A custom update checker for WordPress plugins. Useful if you don't want to host your project in the official WP repository, but would still like it to support automatic updates. Despite the name, it also works with themes.

2,568 stars438 forksPHPMIT

At a glance

What is it?
A PHP library that gives commercial plugins and private themes the same update notifications and one-click upgrades as WordPress.org listings, driven by a JSON file or a GitHub, GitLab or BitBucket repository.
Who is it for?
Adopt it if you sell or distribute a WordPress plugin or theme outside WordPress.org and want the standard dashboard update flow without building an update server. Do not adopt it if your code is already hosted on WordPress.org, since that infrastructure already handles this.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The distribution gap this library fills

WordPress core only knows how to check for updates against WordPress.org. A plugin sold from your own site, a theme bundled with a client project, or an internal tool kept in a private repository gets no update notification, no version comparison, and no one-click upgrade. Users have to be told by email that a new ZIP exists, then download and install it by hand.

Plugin Update Checker closes that gap from inside the plugin itself. The README describes the goal plainly: it "lets you add automatic update notifications and one-click upgrades to your commercial plugins, private themes, and so on." The audience is plugin and theme authors who distribute outside the official directory, plus agencies maintaining private code across many client sites.

The important design decision is that it does not build a parallel update screen. It hooks into the upgrade UI WordPress already ships, so from the user's side the experience is indistinguishable from a plugin hosted on WordPress.org.

How the update check actually runs

There is no daemon and no server component in the library itself. The plugin you ship carries the checker, and the checker polls a URL you control.

For self-hosted distribution, that URL returns a JSON document holding the current version, a download URL, and display metadata. The library compares the version in that document against the installed version and, if the remote one is newer, injects an update notice into the normal WordPress upgrade flow. The README states that by default the library checks the specified URL every 12 hours.

GitHub, GitLab and BitBucket integrations replace the JSON file with the repository API, and the README notes a branch can be designated as the stable release channel via setBranch. Themes work through the same machinery, which is why the name undersells the project.

One placement rule matters more than it looks. The README recommends instantiating the checker during plugins_loaded or outside any hooks, and warns that doing it only during an admin_* action means "updates will not be visible to a wide variety of WordPress management tools; they will only be visible to logged-in users on dashboard pages." If you manage sites with an external dashboard, that single line decides whether your updates appear there at all.

Installing Plugin Update Checker and wiring a first update source

The library is not installed from Packagist as a standalone service. The README's first step is to download the latest release and copy the plugin-update-checker directory into your plugin or theme. A composer.json exists at the repository root, and the README notes that with the Composer autoloader you do not need to require the library explicitly.

Start with the self-hosted path, because it makes the data flow visible. Create a JSON file from the plugin example in the examples directory and replace the placeholder values:

json
{
  "name" : "Plugin Name",
  "version" : "2.0",
  "download_url" : "https://example.com/plugin-name-2.0.zip",
  "sections" : {
    "description" : "Plugin description here. You can use HTML."
  }
}

Upload that file somewhere publicly reachable. Then load the library and point it at the file from your main plugin file:

php
require 'path/to/plugin-update-checker/plugin-update-checker.php';
use YahnisElsts\PluginUpdateChecker\v5\PucFactory;

$myUpdateChecker = PucFactory::buildUpdateChecker(
	'https://example.com/path/to/details.json',
	__FILE__,
	'unique-plugin-or-theme-slug'
);

The second argument must be the absolute path to the main plugin file or any file in the theme directory; the README says you can use the __FILE__ constant if you followed the getting started instructions. The third argument is the slug, optional but recommended. Set it to your plugin directory name. If you omit it, the library derives the slug from the main plugin file name, and the README warns this "can lead to conflicts if your plugin has a generic file name like plugin.php."

To publish an update, change the version number in the JSON file and make sure download_url points at the new ZIP. The README suggests wp-update-server to automate that step. On the test site you can force an immediate check by clicking "Check for updates" on the Plugins page; themes lack that link, so the README's alternative is the Debug Bar plugin, opening the "PUC (your-slug)" panel and clicking "Check Now".

For a GitHub source, the same factory call takes the repository URL, then setBranch names the stable branch:

php
$myUpdateChecker = PucFactory::buildUpdateChecker(
	'https://github.com/user-name/repo-name/',
	__FILE__,
	'unique-plugin-or-theme-slug'
);

$myUpdateChecker->setBranch('stable-branch-name');

Private repositories need setAuthentication with an access token. Plugins using the GitHub route also need a readme.txt formatted to the WordPress.org plugin readme standard, because its contents are what the "View version 1.2.3 details" link displays.

Where the library stops and your release process begins

The checker is a comparison and notification layer. It does not host anything, does not build ZIPs, and does not decide what "latest" means beyond reading the version string you published.

That produces a specific failure mode: if you ship a new ZIP but forget to bump version in the JSON file, or point download_url at the old archive, users see nothing. There is no error surfaced to the end user, because from the library's perspective the installed version is current. The same applies on GitHub, where the branch you named must actually contain the release you intend.

The README is also silent on rollback. There is no documented path to pin a user to a previous version, and no documented signature or checksum verification for the downloaded package, so integrity rests on HTTPS and on your own hosting. For a paid plugin this is a normal trade-off, but it should be a conscious one.

Finally, this is the wrong tool when the code already lives on WordPress.org. That directory provides update delivery for free, and adding a second checker only creates two sources of truth for the same version number.

Compared with running your own update server

The obvious alternative is a dedicated update server such as wp-update-server, which the README itself references as a way to automate the JSON publishing step. The difference is where the logic sits.

An update server is a service you deploy and operate. It typically scans a directory of ZIPs, extracts version metadata from each package, and serves the same kind of JSON response that Plugin Update Checker consumes. You get less manual editing, but you own a PHP application, a database or file store, and its availability, because every customer site polls it.

Plugin Update Checker inverts that. The client-side library is the only thing you ship, and the "server" can be a static JSON file on any web host, or a repository API you do not run at all. The cost is that the release step stays partly manual unless you add tooling.

For a small commercial plugin, the static file is usually enough and has no uptime story to maintain. For a catalogue of dozens of products with per-customer licensing, the server route starts to pay for itself, which is why the two are frequently used together rather than as strict substitutes.

Licence, versioning and the cost of staying current

The repository is MIT licensed, with license.txt at the root. For plugin authors that is permissive: you can bundle the library inside a commercial plugin without publishing your own source. It does not remove the obligation to keep the bundled copy updated, and the README does not describe an automatic upgrade path for the library itself inside your project.

Versioning is worth reading carefully. The namespace in the README's examples is YahnisElsts\PluginUpdateChecker\v5, and the top-level entry load-v5p7.php indicates the current major line is 5.x. The library is loaded by requiring plugin-update-checker.php, so a major version change is a code change in your plugin, not a silent update.

The README includes a dedicated "Migrating from 4.x" section, which tells you the maintainers treat the 4 to 5 jump as a real migration. Plan for that: pin the bundled copy, read the migration notes before moving, and treat the library as a dependency with its own release cadence rather than a file you drop in once and forget.

Editorial conclusion

Adopt it if you sell or distribute a WordPress plugin or theme outside WordPress.org and want the standard dashboard update flow without building an update server. Do not adopt it if your code is already hosted on WordPress.org, since that infrastructure already handles this. Before shipping, verify that your JSON file is publicly reachable, that the slug you pass to buildUpdateChecker matches your plugin directory, and that your release process actually changes the version field, because a stale version number is the most common way updates silently stop appearing.

Frequently asked questions

How can I check for updates for my WordPress plugins that are not on WordPress.org?

Bundle Plugin Update Checker with your plugin and point it at either a JSON file you host or a GitHub, GitLab or BitBucket repository. The default check interval is 12 hours, and you can force an immediate check from the Plugins page.

How do I release an update with Plugin Update Checker?

For self-hosted distribution, change the version number in the JSON file and make sure download_url points to the latest ZIP. The README suggests wp-update-server to automate that process.

Does Plugin Update Checker work with WordPress themes as well as plugins?

Yes. The README states that despite the name it also works with themes, and PUC uses the theme directory name as the default slug. Themes do not get the "Check for updates" link, so the README points to the Debug Bar plugin instead.

Why is my Plugin Update Checker not showing an update?

The most likely cause is that the published version number was not changed, or download_url still points at the previous archive, since the library only compares version strings. The README also warns that instantiating the checker only during an admin_* action hides updates from many management tools.

Can Plugin Update Checker read updates from a private GitHub repository?

Yes. The README shows calling setAuthentication with an access token on the update checker instance when the repository is private.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. YahnisElsts/plugin-update-checker on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yahniselsts-plugin-update-checker.svg)](https://hysenlabs.com/projects/yahniselsts-plugin-update-checker)