3.1 The automatic updater model

WP_Automatic_Updater applies updates on a schedule without interactive credentials. You hook its decisions and outcomes separately from manual upgrader use.

How it works

During cron, wp_version_check() may call do_action( 'wp_maybe_auto_update' ) when not already inside that action. wp_maybe_auto_update() loads admin includes, instantiates WP_Automatic_Updater, and calls run(). The class uses Automatic_Upgrader_Skin, evaluates is_disabled() and per-item should_update(), selects packages from transients, and runs upgraders.

Inside run(), after acquiring the auto_updater lock, core processes updates in a fixed order: plugins (from update_plugins after wp_update_plugins()), then themes (update_themes after wp_update_themes()), then core (wp_version_check() plus find_core_auto_update()), then translation packs from wp_get_translation_updates().

The automatic_updates_complete hook fires at the end of a batch only when $update_results is non-empty. If nothing ran or nothing recorded results, the hook does not fire. See §4.7 for emails and that hook.

Process flow

  1. Cron or an external trigger runs wp-cron.php.
  2. Update checks may chain to wp_maybe_auto_update.
  3. run() checks is_disabled(). If that check passes, the updater walks the plugin, theme, core, and translation sequence.
  4. Each item downloads and installs through the same upgrader stack as manual flows, with a non-interactive skin.
  5. When results exist, completion fires notification paths and automatic_updates_complete (Part 4).

Reference

ClassFileExtendsRole
WP_Automatic_Updaterclass-wp-automatic-updater.phpBackground decisions and execution; run() order: plugins, themes, core, translations

Developer resources


3.2 Global disable conditions (is_disabled())

The automatic updater short-circuits when the site cannot or must not modify files, or when you explicitly disable it.

How it works

WP_Automatic_Updater::is_disabled() returns true when wp_is_file_mod_allowed( 'automatic_updater' ) is false (for example when DISALLOW_FILE_MODS is true unless filtered), when wp_installing() is true, or when AUTOMATIC_UPDATER_DISABLED is true or the automatic_updater_disabled filter returns true. Disabling the automatic updater also suppresses its notification emails in current core behavior.

Process flow

  1. run() calls is_disabled().
  2. If the method returns true, no automatic updates execute.
  3. Entry points that depend on background updates should surface this state in diagnostics.

Note: should_update() also calls is_disabled() first on every item, so a global disable applies when evaluating offers inside find_core_auto_update() and elsewhere — not only at the start of run().

Reference

ConditionEffect
wp_is_file_mod_allowed( 'automatic_updater' ) returns falseUpdates blocked
wp_installing() returns trueUpdates blocked
AUTOMATIC_UPDATER_DISABLED is true or automatic_updater_disabled returns trueUpdates blocked

Developer resources


3.3 Per-item eligibility (should_update())

Each candidate item passes through should_update( $type, $item, $context ), which merges filesystem reality, VCS detection, API flags, opt-ins, filters, and compatibility checks.

How it works

should_update() begins by calling is_disabled() again. If the automatic updater is globally disabled, the method returns false before any per-item logic, so find_core_auto_update() and other callers also respect the global disable.

The remaining checks combine: non-interactive filesystem access (background cannot show FTP forms); VCS checkout detection (see §3.4); per-type allow rules (Core_Upgrader::should_update_to_version() for core, or autoupdate plus auto_update_plugins and auto_update_themes for plugins and themes, or autoupdate for translations); disable_autoupdate on the offer (see §1.4); apply_filters( "auto_update_{$type}", … ); then for core, PHP and MySQL checks against the offer; for plugins and themes, requires_php if set.

For core, the PHP and MySQL compatibility checks run after disable_autoupdate and auto_update_core, not at the start (see §3.7).

Process flow

  1. should_update() calls is_disabled(). If true, the method returns false immediately.
  2. Core checks whether the filesystem supports non-interactive writes.
  3. VCS checkout detection runs for the relevant path.
  4. Type-specific allow rules evaluate: branch policy for core, or autoupdate plus opt-in arrays for plugins and themes.
  5. The disable_autoupdate property may force the result to false.
  6. The auto_update_{$type} filter runs.
  7. For core: PHP and MySQL compatibility checks evaluate against the offer. For plugins and themes: requires_php evaluates against runtime PHP.
  8. A false result at any step aborts that item. A true result proceeds to download and install.
  9. The pre_auto_update action fires once per item before the attempt (see §6.5).

Reference

CheckTypical outcome if failed
is_disabled()Global disable; returns false (short-circuit)
FilesystemNo silent write capability; returns false
VCSCheckout detected; returns false (unless filtered)
Type allow (branch policy, autoupdate, or opt-in lists)Returns false
disable_autoupdateForces false before auto_update_{$type}; filter may re-enable
auto_update_{$type}Filter returns false; returns false (core may still trigger notification email)
PHP or MySQL (core) or requires_php (plugin or theme)Incompatible runtime; returns false

Developer resources


3.4 VCS checkout detection

Automatic updates skip paths that look like version control working copies to avoid clobbering developer or deployment layouts.

How it works

WP_Automatic_Updater::is_vcs_checkout() walks upward from the package path and always checks ABSPATH for markers .svn, .git, .hg, or .bzr. A match disables auto-updates for that item without an admin notice. The filter automatic_updates_is_vcs_checkout receives ( bool $checkout, string $context ) and may return false to override the detection (for example on CI or managed hosts).

A Git clone of core or a plugin under wp-content can silently block automatic updates for that path.

Process flow

  1. should_update() calls VCS detection for the relevant context.
  2. If detection returns true, the item is skipped unless the filter clears the flag.
  3. Manual updates through the admin interface may still proceed with appropriate credentials.

Reference

HookTypeParametersNotes
automatic_updates_is_vcs_checkoutfilter$checkout, $contextOverride VCS detection

Developer resources


3.5 PHP and MySQL compatibility guards

Offers may declare required PHP or database versions. Automatic updates refuse incompatible targets before an update attempt begins.

How it works

For core, should_update() compares the PHP version to $item->php_version and MySQL to $wpdb->db_version() unless a drop-in replaces MySQL. A mismatch returns false with no update attempt. For plugins and themes, if $item->requires_php is set and runtime PHP is lower, the result is false.

These checks run inside should_update() after filesystem and VCS guards, type-specific allow rules, disable_autoupdate, and auto_update_{$type} — not as the first gate (see §3.3 and §3.7).

Process flow

  1. Earlier guards may already return false.
  2. If the item is still eligible, should_update() applies PHP and MySQL checks (core) or requires_php (plugins and themes).
  3. An incompatible runtime returns false before update() runs.

Reference

TypeGuard
CorePHP and MySQL version comparison against the offer
Plugin or themerequires_php comparison against runtime

Developer resources


3.6 find_core_auto_update(): selecting the core offer

Only one core automatic offer runs per batch. find_core_auto_update() picks it from the update_core transient.

How it works

The function reads the update_core site transient, iterates the updates array, and skips any offer whose response is not autoupdate. For each remaining offer, it calls should_update( 'core', $update, ABSPATH ). It then picks the highest version among passing offers using version comparison on the current property, or returns false if none qualify.

A core auto-update never runs unless the offer exists with response === 'autoupdate' and should_update() passes. Manual-only entries in the interface do not flow through this selector.

Process flow

  1. Core loads the update_core transient.
  2. Offers without response === 'autoupdate' are dropped.
  3. Each candidate passes through should_update().
  4. The highest-passing version wins.
  5. If none qualify, the function returns false and no automatic core update runs this batch.

Reference

FunctionFileReturnsNotes
find_core_auto_update()wp-admin/includes/update.phpobject or falseSelects the automatic core offer

Developer resources


3.7 Core branch policy and resolution order

Automatic core updates combine constant policy, site options, branch filters, offer vetoes, and the per-offer auto_update_core filter. The order below is the resolution chain you must reason about.

How it works

find_core_auto_update() considers only offers with response === 'autoupdate', then calls should_update( 'core', $update, ABSPATH ) for each candidate.

Core_Upgrader::should_update_to_version() applies the WP_AUTO_UPDATE_CORE constant when defined, or else the site options auto_update_core_dev, auto_update_core_minor, and auto_update_core_major. It also evaluates failure-history checks, branch logic, and the filters allow_dev_auto_core_updates, allow_minor_auto_core_updates, and allow_major_auto_core_updates depending on the branch. Each filter receives the boolean derived from constants and options.

The disable_autoupdate property on the offer can force the pending decision to false; filters may still re-enable the item. Then apply_filters( 'auto_update_core', $update, $item ) runs. PHP and MySQL compatibility checks on the offer follow.

Note: apply_filters( 'wp_auto_update_core', … ) is not invoked by Core_Upgrader in core as of inspected versions. Do not confuse this filter name with the site option or constant handling. Policy for which classes of core update are allowed lives in should_update_to_version(). The dynamic hook auto_update_core is the per-offer gate inside should_update() after the initial allow or deny for that offer is computed.

Process flow

  1. Only offers with response === 'autoupdate' are considered.
  2. Core_Upgrader::should_update_to_version() evaluates the constant, auto_update_core_* options, failure history, branch logic, and allow_*_auto_core_updates filters — yielding a boolean.
  3. disable_autoupdate on the offer can force false; filters may re-enable.
  4. apply_filters( 'auto_update_core', $update, $item ) runs.
  5. PHP and MySQL compatibility checks on the offer follow.

Reference

HookTypeParametersNotes
allow_dev_auto_core_updatesfilterboolDevelopment build installs
allow_minor_auto_core_updatesfilterboolSame x.y branch
allow_major_auto_core_updatesfilterboolx.y to x.y+1
auto_update_corefilterbool, objectAfter the initial core decision for the offer

Developer resources


3.8 Per-plugin and per-theme auto-update preferences

Site options list which plugins and themes have opted into automatic updates. These options merge with autoupdate flags on offers.

How it works

auto_update_plugins stores an array of plugin basenames. auto_update_themes stores opted-in theme identifiers. The AJAX action toggle-auto-updates persists these options without running the upgrader. should_update() merges them with the offer’s autoupdate flag when automatic updates are enabled for the type.

Multisite restricts who may change these toggles (see §5.11 and §5.4).

Process flow

  1. A user toggles the interface control, or a network policy change updates the options.
  2. The next should_update() call reads the opt-in arrays.
  3. An offer without autoupdate still requires opt-in for that item unless a filter changes the outcome.

Reference

NameTypeDefaultEffect
auto_update_pluginssite option (array)emptyPlugin basenames opted into auto-updates
auto_update_themessite option (array)emptyTheme identifiers opted into auto-updates

Developer resources


3.9 Translation auto-updates and async chaining

Translations update automatically with their own filters and may install in a chained pass after another upgrade completes.

How it works

Two filters apply in different contexts.

async_update_translation runs inside Language_Pack_Upgrader::async_upgrade() only after a successful core, plugin, or theme upgrade in the same request. It governs whether to bulk-install translation packs in the follow-up pass; the default pending decision follows autoupdate on each language offer. Before installing, async_upgrade() returns without action if WP_Automatic_Updater::is_vcs_checkout( WP_CONTENT_DIR ) detects a VCS checkout under wp-content, matching the cautious behavior for that code path.

auto_update_translation participates in should_update() for cron-driven automatic translation updates, like other auto_update_{$type} hooks.

Changing one filter does not automatically change the other.

Process flow

  1. A primary upgrade completes.
  2. The upgrader_process_complete action fires.
  3. At priority 20, async translation upgrade may run (unless VCS detection short-circuits it).
  4. Separately, cron-driven automatic updates evaluate translation items through should_update(), including the auto_update_translation filter.

Reference

HookTypeParametersNotes
async_update_translationfilterboolPost-upgrade chain in same request
auto_update_translationfilterboolBackground should_update() for translations

Developer resources


Author

Photo de Quentin Le Duff

Quentin Le Duff

Quentin Le Duff is a WordPress developer for ten years, Opquast® Expert & ISTQB certified. He works with WordPress every day: developing open source plugins and themes, hosting and maintaining his clients’ websites, and writing about the WP ecosystem.