Enabling updates for self-hosted WordPress themes and updates

By David
30th January 2021

I recently experimented with changing my themes and plugins to support self-hosted updates. For plugins this was relatively straight-forward once I'd found two articles, one by Misha Rudrastyh, which got me to adjust the contents of the zip files down one level (which allows WordPress to understand that the new zip file being uploaded is a replacement/update for a plugin that is already installed) and gave the basics of the self-hosted update process1, including hosting a file that describes the update along side the zip file that is the actual update. The second article by Paul Biron showed what the current version of WordPress is expecting in the php code for plugins

However those articles don't talk about themes that much. To be fair the process for themes is very similar2. However the expection of the WordPress code is different to that for plugins, so as a record here's my code:

function ningxia_check_for_update( $slug ) {
 $info = ningxia_get_remote_update_info( $slug );
 if ( false == $info ) {
  return false;
 if ( version_compare( '@@@VERSION@@@', $info['version'], ' $slug,
    'new_version'   => $info['version'],
    'url'           => $info['homepage'],
    'package'       => $info['download_link'],
    'icons'         => array(),
    'banners'       => array(),
    'banners_rtl'   => array(),
    'tested'        => '',
    'requires_php'  => '',
    'compatability' => new StdClass()
  return $update;
 } else {
  return false;

function ningxia_pre_set_site_transient_update_themes( $transient ) {
 // Check for update
 $themeslug = 'Ningxia';
 $update = ningxia_check_for_update( $themeslug );
 if ( $update ) {
  $transient->response[$themeslug] = $update;
 return $transient;
add_filter( 'pre_set_site_transient_update_themes', 'ningxia_pre_set_site_transient_update_themes' );

My build process modifies the @@@VERSION@@@ string into the current version number for the build being created (for example, 3.80.1) whereas the $info[['version']] comes from the json that been retrieved from the self-hosting site.

Last updated: May 30, 2021 at 9:36 am


  1. Though I did have to adjust the logic slightly - when you detect that the filter call is not for the current plugin you need to return the current value of first parameter and not just return false
  2. Though the articles I've read suggest that there could be differences for multi-site setups - as this site is not a multi-site setup, I've yet to test how well this description works for that kind of setup