Some WordPress sites must not change unless you change them: a client site under a maintenance contract that you update on staging first, a site deployed from Git, or a locked-down kiosk. On those sites the automatic updates, the update checks and the red update counts in wp-admin only get in the way. This snippet disables all WordPress updates (core, plugins, themes and translations) and the update notices.
Read this first: it also stops security updates. Only use it when you update the site yourself, for example with wp core update and wp plugin update --all in WP-CLI or in your deployment. If you only want to stop the automatic updates and keep security fixes, use Disable WordPress Automatic Updates but Keep Security Fixes instead.
The snippet
<?php
/**
* Disable WordPress core, plugin, theme and translation updates,
* the update checks behind them and the update notices in wp-admin.
*
* Warning: this also stops security updates. Update by hand or with WP-CLI.
*/
// Automatic (background) updates.
add_filter( 'automatic_updater_disabled', '__return_true' );
add_filter( 'auto_update_core', '__return_false' );
add_filter( 'auto_update_plugin', '__return_false' );
add_filter( 'auto_update_theme', '__return_false' );
add_filter( 'auto_update_translation', '__return_false' );
// Hide the "Enable auto-updates" links on the Plugins and Themes screens.
add_filter( 'plugins_auto_update_enabled', '__return_false' );
add_filter( 'themes_auto_update_enabled', '__return_false' );
// Update checks against WordPress.org: the twice-daily cron jobs and the checks on admin screens.
remove_action( 'init', 'wp_schedule_update_checks' );
remove_action( 'wp_version_check', 'wp_version_check' );
remove_action( 'wp_update_plugins', 'wp_update_plugins' );
remove_action( 'wp_update_themes', 'wp_update_themes' );
remove_action( 'wp_maybe_auto_update', 'wp_maybe_auto_update' );
remove_action( 'admin_init', '_maybe_update_core' );
remove_action( 'admin_init', '_maybe_update_plugins' );
remove_action( 'admin_init', '_maybe_update_themes' );
remove_action( 'load-plugins.php', 'wp_update_plugins' );
remove_action( 'load-update.php', 'wp_update_plugins' );
remove_action( 'load-update-core.php', 'wp_update_plugins' );
remove_action( 'load-themes.php', 'wp_update_themes' );
remove_action( 'load-update.php', 'wp_update_themes' );
remove_action( 'load-update-core.php', 'wp_update_themes' );
// Ignore update data that was saved before the snippet ran, so no counts or "update now" links show.
add_filter( 'pre_site_transient_update_core', '__return_null' );
add_filter( 'pre_site_transient_update_plugins', '__return_null' );
add_filter( 'pre_site_transient_update_themes', '__return_null' );
// wp-admin adds these after plugins load, so remove them on admin_init.
add_action( 'admin_init', 'dpc_disable_update_admin_hooks' );
function dpc_disable_update_admin_hooks(): void {
remove_action( 'admin_notices', 'update_nag', 3 );
remove_action( 'network_admin_notices', 'update_nag', 3 );
remove_action( 'admin_notices', 'maintenance_nag', 10 );
remove_action( 'network_admin_notices', 'maintenance_nag', 10 );
remove_action( 'upgrader_process_complete', 'wp_version_check', 10 );
remove_action( 'upgrader_process_complete', 'wp_update_plugins', 10 );
remove_action( 'upgrader_process_complete', 'wp_update_themes', 10 );
}
Add it with Scripts Organizer
In Scripts Organizer go to Code Blocks → Add New and give the block a name. Set Trigger location to Everywhere, tick PHP under Script location, paste the code into the PHP editor and publish. Leave Action/Hook empty. Everywhere is needed because the update checks also run from WP-Cron, in background requests that are neither wp-admin nor a front-end page.
The older version of this post said to set Action to plugins_loaded. That is not needed: the code only adds and removes hooks, and that works as soon as Scripts Organizer loads it. The same code also works unchanged in an mu-plugin, which is the safer place on client sites because nobody can deactivate it from the Plugins screen.
How it works
automatic_updater_disabledswitches off background updates completely. Theauto_update_*filters return false as well, in case another plugin turns single updates back on. The two*_auto_update_enabledfilters hide the Enable auto-updates links on the Plugins and Themes screens.- WordPress asks WordPress.org for updates twice a day from WP-Cron and again when you open wp-admin, the Plugins, Themes or Updates screen. The
remove_action()calls unhook all of those checks, so the site makes no update requests at all. - Update data saved before you added the snippet would keep showing old counts. The
pre_site_transient_update_*filters returnnull, so WordPress acts as if it never checked. - The update nag (“WordPress 6.x is available! Please update now.”) and the after-upgrade checks are added by wp-admin itself, after plugins load.
dpc_disable_update_admin_hooks()removes them onadmin_init, when they exist.
The older version removed the update nag on init. In wp-admin that runs before the nag is added, so the removal did nothing, and the update checks kept running in the background.
Customise it
- Keep translation updates: delete the
auto_update_translationline. - Keep plugin updates but block core: delete the plugin lines (
auto_update_plugin, thewp_update_pluginsand_maybe_update_pluginslines andpre_site_transient_update_plugins). - Only hide the notices from clients: wrap the
remove_action()calls indpc_disable_update_admin_hooks()inif ( ! current_user_can( 'manage_options' ) )and leave the update checks alone.
Gotchas
- WP-Cron keeps the already scheduled
wp_version_check,wp_update_pluginsandwp_update_themesevents, but nothing runs when they fire. Remove them withwp cron event delete wp_version_check(and the other two) if you want a clean list. - Dashboard → Updates still opens and says everything is up to date. That is not true, so check for updates with WP-CLI (
wp plugin list --update=available) on a schedule. - Plugins with their own updater (many premium plugins) may still show their notices. Their update checks are not part of WordPress core.
- Defining
AUTOMATIC_UPDATER_DISABLEDinwp-config.phponly stops background updates; it does not stop the checks or the notices.
In the Scripts Organizer library Import it with one click from Scripts Organizer.