This guide explains how to build a manual WordPress theme updater using core APIs, with Init Press as a concrete example. The result integrates seamlessly into the native WordPress update workflow.
Overview of the Custom Theme Update Architecture
A standard custom theme update system consists of three core components:
- The installed theme on the user’s WordPress site
- A publicly accessible JSON file describing the latest version
- A ZIP package containing the updated theme files
WordPress periodically checks for updates via internal transients. By hooking into these checks, a theme can announce its own updates without relying on WordPress.org.
The Version Information JSON File
The JSON file acts as a lightweight version API. It should be publicly accessible and return reliable metadata about the latest release.
{
"name": "Init Press",
"slug": "init-press",
"version": "1.1.0",
"author": "Init HTML",
"homepage": "https://inithtml.com/theme/init-press/",
"description": "A clean, lightweight, and UIkit-powered WordPress theme for blogs, content sites, and modern publications.",
"requires": "6.3",
"tested": "6.9",
"requires_php": "7.4"
}
The version field is critical, as it is used to determine whether an update is available.
Hooking into WordPress Theme Updates
The update logic should run only in the admin area. WordPress exposes the site_transient_update_themes filter, which allows themes to register update data during the update check cycle.
/**
* Theme updater
*/
if ( is_admin() ) {
add_filter( 'site_transient_update_themes', function ( $transient ) {
$theme_slug = 'init-press'; // theme folder name
$theme = wp_get_theme( $theme_slug );
if ( ! $theme->exists() ) {
return $transient;
}
$current_ver = $theme->get( 'Version' );
// Version metadata JSON
$json_url = 'https://inithtml.com/releases/themes/init-press.json?nocache=' . time();
$res = wp_remote_get( $json_url, [
'timeout' => 10,
]);
if ( is_wp_error( $res ) || wp_remote_retrieve_response_code( $res ) !== 200 ) {
return $transient;
}
$info = json_decode( wp_remote_retrieve_body( $res ) );
if ( ! $info || empty( $info->version ) ) {
return $transient;
}
if ( version_compare( $info->version, $current_ver, '>' ) ) {
if ( ! is_object( $transient ) ) {
$transient = new stdClass();
}
$transient->response[ $theme_slug ] = [
'theme' => $theme_slug,
'new_version' => $info->version,
'url' => $info->homepage ?? '',
'package' => 'https://inithtml.com/releases/themes/init-press.zip?ver=' . $info->version,
];
}
return $transient;
});
}
How the Update Flow Works
The update process follows a clear sequence:
- Retrieve the installed theme information using
wp_get_theme() - Read the currently installed version from
style.css - Fetch the remote JSON metadata via HTTP
- Compare versions using
version_compare() - Inject update data into the WordPress update transient
Once registered, WordPress displays the update notification and handles the download and installation automatically.
Understanding the Update Response Fields
theme: the theme directory slugnew_version: the latest available versionurl: the theme’s homepage or changelog pagepackage: the ZIP file URL used for updating
The ZIP archive must contain a root folder matching the theme slug, otherwise the update will fail.
Production Considerations
- Enable server-side caching for the JSON endpoint
- Protect ZIP downloads using tokens or signed URLs if needed
- Keep update logic strictly within the admin context
- Always test updates on a staging environment first
Conclusion
A custom theme updater allows independently distributed WordPress themes to deliver a first-class update experience. By leveraging site_transient_update_themes, updates integrate naturally with WordPress core while remaining fully under the developer’s control.
This approach is essential for commercial themes, private client projects, and any WordPress ecosystem that requires reliable version management outside of WordPress.org.
Comments