All guides

WordPress update log: see exactly which update broke your site

· 6 min read

WordPress 7.1 shipped on 19 August 2026. Within hours, sites running WP Rocket began returning a white screen or the generic critical error page, with the front end, wp-admin, the REST API and WP-CLI all down at once. The cause was a type error in WP Rocket's Cloudflare integration, and the fix, version 3.23.2.2, arrived the following day.

For the people whose sites went dark, the fix was not the hard part. The hard part was the half hour before it, spent working out what had changed. WordPress keeps no update log. There is no screen anywhere in wp-admin that tells you core updated at 01:52 and which plugins auto-updated alongside it, so the first question after any outage gets answered by guesswork.

This guide covers what actually broke, what the incident says about leaning on a large plugin, and a small plugin you can build to keep the record WordPress does not.

What one build gives you

  • A Tools > Update Log screen listing every core, plugin and theme version change, newest first
  • The version before and the version after on each row, with the date and time in the site's timezone
  • Installs, updates, removals, activations and deactivations, including changes made over SFTP or WP-CLI
  • Attribution to the logged-in user where there was one, and an automatic marker for background updates
  • Filtering by type and date range, plus CSV export of the filtered view

What it does not do

  • Rolling back an update: this records what changed, it does not restore anything
  • Anything at all during an outage, because a fatal error stops this plugin loading too
  • File integrity checking inside a plugin, which is a different job and a bigger one
  • Logins, post edits, option changes and the rest of what an activity log covers
  • Alerting: there is no email or Slack message when something updates

Simple History does these. This covers the part most sites use.

The prompt

Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.

Build a WordPress plugin that logs every version change to WordPress core, plugins and themes. On activation, create a custom database table with columns for timestamp, type (core, plugin or theme), item name, item slug or file path, old version, new version, action (installed, updated, removed, activated or deactivated), and the user ID that triggered it. Store a snapshot of the current versions of core, all installed plugins and all installed themes in a single option. Compare the live versions against that snapshot on the upgrader_process_complete hook, and again on admin_init throttled to once every fifteen minutes with a transient, so changes made over SFTP or WP-CLI are caught too. Write a row for every difference found, then update the snapshot. Add an admin page under Tools > Update Log that lists the rows newest first, with columns for date and time in the site timezone, type, name, from version, to version, action, and who triggered it, showing rows with no logged-in user as Automatic. Add a dropdown to filter by type and two date fields to filter by range, and an Export CSV button that exports the currently filtered rows. Add a daily cron event that deletes rows older than 365 days.

Build this pluginAbout 55 credits · the free plan includes enough for one

What actually broke

WordPress 7.1 changed how it builds the identifier for a hook callback. It moved from spl_object_hash(), which returns a long hexadecimal string, to spl_object_id(), which returns a small number. PHP stores a numeric string array key as an integer, so any code that walked the callback list assuming every key was a string started receiving integers instead.

WP Rocket's Cloudflare module did exactly that, calling substr() on each key. The file declares strict_types=1, so PHP raised a TypeError rather than quietly casting the number to a string. The error surfaced in inc/ThirdParty/Plugins/CDN/Cloudflare.php, on a path that runs on every request, which is why entire sites went down instead of one page.

Version 3.23.2.1 and earlier are affected. 3.23.2.2, released on 20 August 2026, fixes it. If a site is still down and wp-admin is unreachable, rename the wp-content/plugins/wp-rocket folder over SFTP, or run wp plugin deactivate wp-rocket over WP-CLI, then update and switch it back on.

The part people are angry about

The bug was filed on WP Rocket's GitHub on 6 July, during the WordPress 7.1 beta, six weeks before release. The report included the one-line fix. It sat there until production sites started failing, and the release that went out on 20 August was that same one-line change.

That is the complaint doing the rounds: not that a plugin had a bug, which happens to every plugin eventually, but that the beta period did its job. The break was caught, written up and solved by somebody outside the company, in public, with six weeks to spare. WordPress runs a months-long public beta precisely so this class of failure never reaches a live site.

It is worth keeping in proportion. WP Rocket shipped a fix inside a day of the first production report, which is faster than plenty of vendors manage, and the change in core was a legitimate improvement that any plugin reading hook internals was exposed to.

The implication worth taking seriously

The file that crashed is a Cloudflare integration: a module for a service that plenty of WP Rocket users have never switched on. That is the part to sit with. When you install a large plugin you take on all of its code, not the slice of it you use, and a caching plugin runs on every single request. The blast radius of a dependency is its whole surface.

The second half of the problem is the waiting. When something you did not write breaks, your options are to disable it and lose the feature, patch it and watch the patch get overwritten by the next update, or wait for the vendor. Here the wait was one day. It is not always one day, and you do not get to choose.

None of this is an argument against WP Rocket. Page caching, critical CSS and file optimisation are genuinely difficult, and rolling your own is a worse idea than installing a good one. It is an argument for two smaller things: knowing what is actually running on your site, and being able to answer the question what changed without guessing.

What the plugin does

A Steem build for this adds a single admin screen under Tools > Update Log, backed by its own table. It keeps a snapshot of the installed version of core, every plugin and every theme, compares the live versions against that snapshot, and writes a row for each difference it finds.

When you should just install Simple History

If what you want is an activity log, install Simple History. It is free, it records updates alongside logins, post edits and option changes, and it has been maintained for years by people who do this properly. For most sites that is the right answer, and this guide is not trying to talk anyone out of it.

The case for building your own is narrow and worth stating plainly. You want update history and nothing else, on a client site where adding a broad logging plugin is a conversation rather than a click. Or you want the screen shaped to how you actually work, with your columns and your retention. That is a small, single-purpose plugin, and small single-purpose plugins are the ones worth owning outright rather than renting.

One honest limit before you build it: this does not help you during an outage. If a fatal error is stopping WordPress from loading, it is stopping this plugin from loading too, and nothing gets written. What it gives you is the record of what changed in the hours before, ready to read the moment the site is back.

Questions

Will this tell me what broke my site while the site is down?
No. A fatal error stops this plugin loading, so nothing is written during the outage. Recover first, by deactivating the suspect plugin over SFTP or WP-CLI, then read the log to see exactly what changed in the hours beforehand.
Does it catch updates made over WP-CLI or SFTP?
Yes, on the next admin page load. It compares live versions against its last snapshot rather than only listening to the updater, so a change made outside wp-admin still produces a row. The timestamp is when the change was noticed, not when it happened.
Does this stop WordPress auto-updates?
No, it only records them. Controlling which updates run automatically is a separate job, handled by constants in wp-config.php or by a plugin built for that purpose.
Why not just use Simple History?
For most sites, do. It is free and it logs far more than updates. Build this instead when you want update history only, with nothing else recorded, or when you want the screen shaped to your own workflow.
How much data does it store?
One row per version change, so the table grows with how often you update rather than with your traffic. The plugin keeps a year by default and prunes older rows on a daily cron.

The prompt

Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.

Build a WordPress plugin that logs every version change to WordPress core, plugins and themes. On activation, create a custom database table with columns for timestamp, type (core, plugin or theme), item name, item slug or file path, old version, new version, action (installed, updated, removed, activated or deactivated), and the user ID that triggered it. Store a snapshot of the current versions of core, all installed plugins and all installed themes in a single option. Compare the live versions against that snapshot on the upgrader_process_complete hook, and again on admin_init throttled to once every fifteen minutes with a transient, so changes made over SFTP or WP-CLI are caught too. Write a row for every difference found, then update the snapshot. Add an admin page under Tools > Update Log that lists the rows newest first, with columns for date and time in the site timezone, type, name, from version, to version, action, and who triggered it, showing rows with no logged-in user as Automatic. Add a dropdown to filter by type and two date fields to filter by range, and an Export CSV button that exports the currently filtered rows. Add a daily cron event that deletes rows older than 365 days.

Build this pluginAbout 55 credits · the free plan includes enough for one

Read next

Other plugins you can build this way

Each loads into the composer, ready to edit.