1.1 The discovery model
Discovery is the phase where WordPress learns what updates exist. Nothing installs until WordPress writes offers to site transients and the admin interface or automatic updater reads them.
How it works
WordPress separates discovery from application and feedback. The functions wp_version_check(), wp_update_plugins(), and wp_update_themes() check for updates — they do not install anything. Their results live in site transients. Application relies on the upgrader stack (see §2.1). Feedback uses notices, badges, skins, or email depending on the entry path (see §4.1–§4.2 and Part 5).
Process flow
- An HTTP client contacts WordPress.org or another host for third-party metadata.
- WordPress decodes the response into offer objects.
- Core merges offers into transient payloads.
- Admin screens and cron read the transients to decide what to display or attempt next.
- If discovery fails, transients remain stale or empty and the site shows no updates until a later check succeeds.
Reference
| Concept | Role |
|---|---|
| Discovery functions | Populate the update_core, update_plugins, and update_themes site transients |
| Application | WP_Upgrader subclasses; discovery alone does not invoke them |
| Feedback | Transients drive the in-admin interface; background email sends only after automatic updates (see §4.1) |
Developer resources
- Plugin API: hooks, actions, and filters — how hooks attach to core behavior.
1.2 Site transients: the update data layer
Site transients hold the latest discovery results. When you alter update behavior, you typically interact with these structures or the filters that wrap them.
How it works
Core caches discovery results in site transients, which are shared network-wide on multisite. wp_version_check() sets the update_core transient. That transient holds the offers array in updates, plus last_checked, version_checked, and optional translation payloads. Checksum maps are not stored here; core fetches them on demand through get_core_checksums() (see §1.9).
wp_update_plugins() sets the update_plugins transient with response, no_update, checked versions, and translation lists. wp_update_themes() sets the update_themes transient following the same pattern for themes.
Dynamic filters such as pre_set_site_transient_{$transient} run before save. This filter is the primary integration point for adjusting or clearing structures before an update runs.
Process flow
- A check function runs and builds or merges a payload.
- Filters may alter the payload before storage.
- WordPress stores the payload as a site transient.
- Readers — including the Updates screen,
wp_get_update_data(), andWP_Automatic_Updater— consume the transient. - Stale transients persist until the next successful check or a forced refresh (see §1.6).
Reference
| Transient | Set by | Typical contents |
|---|---|---|
update_core | wp_version_check() | updates (offers), last_checked, version_checked, translations |
update_plugins | wp_update_plugins() | response, no_update, checked, translations |
update_themes | wp_update_themes() | Same pattern for themes |
Developer resources
wp_update_plugins()reference — plugin update check and transient storage.
1.3 Core branch policy options (auto_update_core_*)
Site options control which branches of WordPress core may receive auto-updates when the WP_AUTO_UPDATE_CORE constant is not defined in wp-config.php. These options work together with filters and the per-offer auto_update_core filter (see §3.7).
How it works
When WP_AUTO_UPDATE_CORE is undefined, core reads site options inside branch policy logic in Core_Upgrader::should_update_to_version(). Values are typically enabled, disabled, or unset, with defaults from the schema and admin interface. These options live in the options table (site options on multisite) — they are not stored inside the update transients. For precedence with WP_AUTO_UPDATE_CORE and filters, see §5.10 and §3.7.
Process flow
- Core evaluates the running version branch: development, minor, or major.
- Core reads the matching
auto_update_core_*option unless the constant overrides policy. - Branch filters may change the boolean result.
- The result feeds
should_update()together with the offer and other guards.
Reference
| Name | Type | Default | Effect |
|---|---|---|---|
auto_update_core_dev | site option | Per schema | Development or nightly-style builds on the current branch |
auto_update_core_minor | site option | Per schema | Minor releases within the same x.y branch |
auto_update_core_major | site option | Per schema | Major releases; the WordPress 5.6+ interface maps choices here |
Developer resources
WP_AUTO_UPDATE_COREinwp-config.php— constant that overrides branch options when defined.
1.4 The autoupdate and disable_autoupdate offer flags
API responses attach flags to each offer. These flags determine whether an item is a candidate for unattended updates before per-item filters run.
How it works
When WordPress.org answers a plugin or theme update-check request, each entry in the response map is an offer object. Plugins and themes with an Update URI header can receive offers from update_plugins_{$hostname} or update_themes_{$hostname} instead; those objects may also include autoupdate and disable_autoupdate.
An autoupdate boolean signals that the build is eligible for unattended auto-update from the directory or third-party provider’s perspective. In WP_Automatic_Updater::should_update(), for plugins and themes, core first sets the pending decision from autoupdate, then merges per-item site option opt-ins (auto_update_plugins, auto_update_themes) when auto-updates are enabled for the type. If autoupdate is empty and the site administrator has not opted the item in, the pending decision stays false.
The disable_autoupdate property forces the pending decision to false before apply_filters( "auto_update_{$type}", … ) runs, but the filter may still return true to re-enable the update.
These flags are not durable site settings. They arrive on each HTTP response or from a filter callback. The transient stores whatever the last check returned. Refresh offers with wp_update_plugins() or wp_update_themes() before relying on offer objects for policy.
Process flow
- Discovery returns offers, each carrying optional
autoupdateanddisable_autoupdateflags. should_update()combines the flags with opt-in arrays and filters.- A false outcome skips automatic installation for that item unless a filter overrides the decision.
Reference
| Flag | Meaning |
|---|---|
autoupdate | The directory, third-party Update URI provider, or author signals eligibility for automatic update |
disable_autoupdate | Vetoes the item before auto_update_{$type} runs; filters may override |
Developer resources
WP_Automatic_Updater::should_update()reference — eligibility stack for automatic updates.
1.5 Scheduled checks (WP-Cron)
WordPress schedules recurring update checks so discovery runs without a logged-in user. You need to understand cron behavior when DISABLE_WP_CRON or external schedulers replace the default mechanism.
How it works
wp_schedule_update_checks() runs on init and schedules three twicedaily events: wp_version_check calls wp_version_check(), wp_update_plugins calls wp_update_plugins(), and wp_update_themes calls wp_update_themes().
When DISABLE_WP_CRON is true, WordPress does not spawn the cron runner on normal page loads. Scheduled hooks — including wp_version_check, wp_update_plugins, wp_update_themes, and by chaining wp_maybe_auto_update — do not run unless something invokes wp-cron.php (system cron, monitoring, or WP-CLI). Admin-triggered checks described in §1.6 still run on those requests because they are not cron-based.
Process flow
- The
inithook schedules events if they are not already scheduled. - On each cron run, due events fire.
- Each handler calls the matching update function.
- Throttling inside those functions may skip the HTTP request if a recent check is still valid, unless the caller forces a refresh (§1.6).
- Failure leaves transients unchanged until the next successful run.
Reference
| Hook | Type | Parameters | Notes |
|---|---|---|---|
wp_version_check | cron event | — | Calls wp_version_check() |
wp_update_plugins | cron event | — | Calls wp_update_plugins() |
wp_update_themes | cron event | — | Calls wp_update_themes() |
Developer resources
- Disabling WP-Cron in
wp-config.php— behavior when cron is disabled. wp cron event runCLI command — running due events manually.
1.6 Admin-triggered checks and throttle bypass
Loading specific admin screens forces fresher checks with shorter throttles than the default long backoffs. If you add a custom “check for updates” control, mirror these patterns.
How it works
Hooks on admin screens such as load-plugins.php, load-update.php, and load-update-core.php trigger plugin or theme checks with context-dependent timeouts. The Updates screen uses roughly a one-minute backoff, list tables use roughly one hour, and the default backoff is roughly 12 hours.
_maybe_update_plugins() and _maybe_update_themes() on admin_init apply a 12-hour backoff when last_checked is recent. _maybe_update_core() uses the same 12-hour window only when the transient’s version_checked still matches the running WordPress version — if the versions differ, a check runs immediately.
wp_version_check() accepts a second parameter $force_check. When true, the function skips the timeout against the stored update_core transient and issues a fresh HTTP request. wp_update_plugins() and wp_update_themes() accept $extra_stats; when that array is non-empty, the early-return path for “checked recently and nothing changed” is skipped, comparable to a forced check. A non-empty first argument to wp_version_check() can also set $force_check internally per wp-includes/update.php.
Note: Do not force checks on every cron or background request. Reserve forced checks for user-initiated or infrequent jobs to avoid excessive requests to WordPress.org.
Process flow
- An admin screen loads and its load hooks run.
- Update functions run with short timeouts or with
$force_checkand non-empty extra stats. - The HTTP request returns new offers.
- WordPress refreshes the transients.
- The interface reflects new update counts.
- If the user lacks sufficient capabilities, WordPress displays limited messaging.
Reference
| Function | File | Returns | Notes |
|---|---|---|---|
wp_version_check() | wp-includes/update.php | void | Pass $force_check as true to skip transient backoff for core |
wp_update_plugins() | wp-includes/update.php | void | Non-empty $extra_stats bypasses the “recently checked” path |
wp_update_themes() | wp-includes/update.php | void | Non-empty $extra_stats bypasses the “recently checked” path |
wp_clean_update_cache() | wp-includes/update.php | void | Clears transients; pair with checks when you need a fresh HTTP request |
Developer resources
wp_version_check()reference — core version check API.wp_clean_update_cache()reference — clears update transients.
1.7 API endpoints
Discovery depends on HTTP endpoints that return JSON offers. SSL availability affects transport and warnings.
How it works
Core sends POST requests to WordPress.org version and update-check endpoints. The URLs in source use http://; when wp_http_supports( array( 'ssl' ) ) returns true, WordPress upgrades the scheme to HTTPS.
The core version check sends query-string parameters plus a POST body that includes encoded translations. Plugin and theme checks POST JSON bodies listing installed components and locales. A separate checksums endpoint serves integrity verification only, not update offers (see §1.9). When SSL is unavailable, core may fall back to HTTP with user-visible warnings in some failure paths.
Process flow
- An update function builds the request body.
- WordPress sends a POST request to the endpoint.
- WordPress decodes the response.
- On error, transients may remain stale or WordPress triggers fallback behavior.
- On success, WordPress updates the transient payloads.
Reference
URL path on api.wordpress.org | Method | Purpose |
|---|---|---|
/core/version-check/1.7/ | POST | Core offers; query arguments plus body (such as translations) |
/plugins/update-check/1.1/ | POST | Per-plugin offers |
/themes/update-check/1.1/ | POST | Per-theme offers |
/core/checksums/1.0/ | GET | Path-to-hash map for a version and locale (§1.9) |
Developer resources
- HTTP API overview — outbound requests from WordPress.
1.8 Third-party update sources (Update URI)
Plugins and themes can declare an Update URI header so that metadata arrives from a host other than WordPress.org. This extends discovery without replacing the core transient model.
How it works
For plugins and themes that declare Update URI, core applies dynamic filters update_plugins_{$hostname} and update_themes_{$hostname}. This mechanism allows commercial or custom directories to supply update metadata. Per-entity auto-update interface preferences are stored in site options and described in §5.4 and §3.8.
Process flow
- Discovery runs.
- Core resolves the hostname from the
Update URIheader. - The matching filter may inject or alter offers.
- WordPress merges results into the standard site transients.
- Automatic updater logic in Part 3 uses those offers like any other.
Reference
| Hook | Type | Parameters | Notes |
|---|---|---|---|
update_plugins_{$hostname} | filter | Varies | Third-party plugin offers |
update_themes_{$hostname} | filter | Varies | Third-party theme offers |
Developer resources
- Theme handbook:
Update URIheader — header purpose for themes; plugins follow the same pattern in core.
1.9 Core integrity verification (checksums)
Checksum verification compares installed files to expected hashes for a given WordPress version. This supports diagnostics and CLI tooling. It does not gate the automatic updater before auto-updates run. Package signature verification on downloaded ZIP files before install is a separate mechanism (§2.12).
How it works
The WordPress.org checksums API returns JSON whose checksums entry maps relative file paths to expected hashes for a core build and locale. Core fetches this map through get_core_checksums() in wp-admin/includes/update.php.
Core_Upgrader::check_files() in wp-admin/includes/class-core-upgrader.php loads checksums and compares md5_file() output to each expected hash, skipping paths under wp-content/. Site Health and upgrade paths may use the same API. The update_core discovery transient does not embed the checksum map — core retrieves it separately when needed.
The WP-CLI command wp core verify-checksums performs a similar integrity check from the command line. Verification is an integrity audit. It is not the same as Ed25519 package signature checks on downloaded ZIP files (§2.12) and does not serve as a blanket pre-flight gate for WP_Automatic_Updater.
Process flow
- A tool or function requests checksums for a specific version and locale.
- Core compares installed files against the hash map.
- A mismatch indicates modified or missing core files.
- The outcome informs administrators but does not by itself control the automatic updater pipeline.
Reference
| Function | File | Returns | Notes |
|---|---|---|---|
get_core_checksums() | wp-admin/includes/update.php | array<string, string> or false | Fetches the path-to-hash map; returns false on failure |
Core_Upgrader::check_files() | wp-admin/includes/class-core-upgrader.php | bool | Compares ABSPATH files to the map |
Developer resources
get_core_checksums()reference — checksum map retrieval.Core_Upgrader::check_files()reference — in-core file comparison.wp core verify-checksumsCLI command — CLI integrity check.
1.10 Exclusions: must-use plugins and drop-ins
Must-use plugins and drop-ins live outside the normal plugin and theme discovery pipelines. They never appear in WordPress.org update check payloads and are not upgraded through the standard update interface or automatic updater paths for ordinary plugins and themes.
How it works
Must-use plugins load from wp-content/mu-plugins/ (and nested PHP files where supported). Core does not enumerate them like regular plugins in wp-content/plugins/, so wp_update_plugins() does not retrieve offers for them from WordPress.org.
Drop-ins (for example advanced-cache.php, db.php, and object-cache.php) are single files or small sets of files placed directly under wp-content/ and registered through get_dropins(). They are not packaged as directory plugins; core has no built-in “update this drop-in from the directory” flow.
Replacing must-use plugins or drop-ins requires manual file deployment, a custom deployment pipeline, or tooling outside the native update mechanisms.
Process flow
- Discovery runs for standard plugins and themes.
- Must-use plugins and drop-ins are excluded from the offer model.
- An administrator or automation tool copies new files into
mu-plugins/orwp-content/(often through version control or CI/CD). - Core does not record version metadata in the update transients for these components.
- Must-use plugins continue loading early in bootstrap; drop-ins take effect when present and valid.
Reference
| Artifact | Discovery | Application |
|---|---|---|
| Must-use plugins | Not listed in the standard plugin update API flow | Manual or external deployment |
| Drop-ins | Not listed as installable packages | Manual or external deployment |
Developer resources
- Must-use plugins — loading rules and constraints.
get_dropins()reference — drop-in filenames and detection.

