How to Handle Theme Updates from your Custom Server

My tutorial about configuring self-hosted plugin updates became quite popular so I decided to publish a similar tutorial related to WordPress themes.

The whole tutorial consists of two steps – in the first step we are going to configure our custom update server, in the second step – create a theme actually.

Okay, let’s dive into it.

1. Configuring Update Server

What is an update server actually?

In reality it is much simpler that may sound. All we need to do is to create a couple files on your hosting, and that would be enough!

In the most of simplicity it could look like this:

the minimum of the files required to create a theme update server
You just need info.json file and an archive with a fresh version of the theme.

I think it is needless to say what is misha-theme.zip, the more interesting file for us is info.json.

The content of info.json file could be completely custom. But quite obviously it should contain the URL the theme archive, and the version of the fresh theme. And let’s add the URL of the page with theme update information.

{
 	"version": "2.0",
 	"details_url": "https://rudrastyh.com/themes/misha-theme/changelog.html",
 	"download_url": "https://rudrastyh.com/themes/misha-theme/2.0.zip"
 }

Bonus. License check

I’ve just showed you a static info.json file that already has everything we need but what if you would like to check if this particular update actually can be installed? For example, if a theme update license is expired.

In this case your info.json file should be transformed into info.php file.

$update = array(
	'version' => '2.0',
	'details_url' => 'https://rudrastyh.com/themes/misha-theme/changelog.html',
	'download_url' => ''
);

if( ! empty( $_GET[ 'license_key' ] ) && license_check_logic( $_GET[ 'license_key' ] ) {
	$update[ 'download_url' ] = 'https://rudrastyh.com/themes/misha-theme/2.0.zip';
}

header( 'Content-Type: application/json' );
echo json_encode( $update );

license_check_logic() is a custom function here that allows to check if the license key $_GET[ 'license_key' ] provided is correct and not expired. A little bit more info is in this tutorial.

automatic update is unavailable

Need some help implementing this custom logic? Me and my team are ready to help.

2. Get the Update Information and Do the Update from your Custom Server

This part is even simpler than the same part in the plugin updater. It is enough to use site_transient_update_themes filter hook here.

Please keep in mind, that I don’t cover performance in this chapter. We will talk about performance later.

add_filter( 'site_transient_update_themes', 'misha_update_themes' );

function misha_update_themes( $transient ) {

	// let's get the theme directory name
	// it will be "misha-theme"
	$stylesheet = get_template();

	// now let's get the theme version
	// but maybe it is better to hardcode it in a constant
	$theme = wp_get_theme();
	$version = $theme->get( 'Version' );


	// connect to a remote server where the update information is stored
	$remote = wp_remote_get(
		'https://rudrastyh.com/wp-content/uploads/theme-updater/info.json',
		array(
			'timeout' => 10,
			'headers' => array(
				'Accept' => 'application/json'
			)
		)
	);

	// do nothing if errors
	if(
		is_wp_error( $remote )
		|| 200 !== wp_remote_retrieve_response_code( $remote )
		|| empty( wp_remote_retrieve_body( $remote ) )
	) {
		return $transient;
	}

	// encode the response body
	$remote = json_decode( wp_remote_retrieve_body( $remote ) );
	
	if( ! $remote ) {
		return $transient; // who knows, maybe JSON is not valid
	}
	
	$data = array(
		'theme' => $stylesheet,
		'url' => $remote->details_url,
		'requires' => $remote->requires,
		'requires_php' => $remote->requires_php,
		'new_version' => $remote->version,
		'package' => $remote->download_url,
	);

	// check all the versions now
	if(
		$remote
		&& version_compare( $version, $remote->version, '<' )
		&& version_compare( $remote->requires, get_bloginfo( 'version' ), '<' )
		&& version_compare( $remote->requires_php, PHP_VERSION, '<' )
	) {

		$transient->response[ $stylesheet ] = $data;

	} else {

		$transient->no_update[ $stylesheet ] = $data;

	}

	return $transient;

}

That already will be enough for your custom theme to receive updates from your custom server.

updating a WordPress theme using a custom updater

But, you may notice that all the admin pages started to load slower. Let’s talk about it in the next chapter.

Performance

The thing is that filter hook site_transient_update_themes is running on every admin page and multiple times.

What can we do? Below is my own solution for that. Maybe it is not perfect, but I am open to your suggestions guys. I decided to use transient cache and also I used the current plugin version as a part of the cache key.

if( false == $remote = get_transient( 'misha-theme-update'.$version ) ) {
	
	// connect to a remote server where the update information is stored
	$remote = wp_remote_get(
		'https://rudrastyh.com/wp-content/uploads/theme-updater/info.json',
		array(
			'timeout' => 10,
			'headers' => array(
				'Accept' => 'application/json'
			)
		)
	);

	// do nothing if errors

	if(
		is_wp_error( $remote )
		|| 200 !== wp_remote_retrieve_response_code( $remote )
		|| empty( wp_remote_retrieve_body( $remote ) )
	) {
		return $transient;
	}

	$remote = json_decode( wp_remote_retrieve_body( $remote ) );

	if( ! $remote ) {
		return $transient; // who knows, maybe JSON is not valid
	}
		
	set_transient( 'misha-theme-update'.$version, $remote, HOUR_IN_SECONDS ); 

}
Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X