To roll back a WordPress plugin, install WP Rollback, open Plugins, click Rollback next to the plugin, and pick the version you were on. That takes two minutes and every guide on the phrase covers it.
The part they leave out is that a rollback is not an undo. It replaces the plugin's files with older files. Everything the newer version wrote to your database stays exactly where it is, in the newer version's format, and the older code has never seen it.
That asymmetry is what turns a rollback into a second outage. This post is about the half nobody documents.
WP Rollback's own plugin page is straightforward about the risk, in a way the tutorials about it are not. It gives "no (zero) assurances, guarantees, or warranties that the plugin, theme, or WordPress version you are downgrading to will work as you expect", and it says to "always be sure you have first tested the rollback on a staging or development site prior to using WP Rollback on a live site" (wordpress.org plugin page, version 3.1.2, read 2026-09-09).
Nobody explains how to do that test. That is the rest of this post.
Core's built-in rollback does not cover the case you are in
WordPress has had a rollback of its own since 6.3, and it is easy to assume it has you covered. It does not, and the distinction matters before you go looking for a backup that is not there.
Core's rollback fires "when anything returns a WP_Error from WP_Upgrader::install_package()": a failed download, an unwritable directory, a package it cannot move into place (Make WordPress Core, "New in 6.3: Rollback for failed manual plugin and theme updates", read 2026-09-09). During the update the old version is copied to wp-content/upgrade-temp-backup/plugins/PLUGINNAME, and if the update succeeds, the backup is deleted.
Read that last clause again. The update that breaks your site is an update that succeeded. The files copied fine, the plugin activated fine, and the checkout is now returning a fatal error two hours later.
Core threw the backup away the moment the file copy worked. Its rollback protects the update mechanism, not your site.
What actually survives a downgrade
Three categories of change outlive the files you just replaced.
Schema. If the newer version added a column or a table with dbDelta, the older code will not drop it. Usually harmless. Occasionally not, if the newer version also made a previously nullable column NOT NULL and the old code inserts rows without it.
The stored version number. Almost every serious plugin records the database version it has migrated to, and it only ever migrates forward.
Data written in the new format. Serialized options, meta values, and custom-table rows written by the newer code stay in the newer shape. The older code reads them with the older expectations. Our post on checking serialized data on a copy covers what that failure looks like when it is quiet rather than fatal.
The second one is the one that surprises people, so it is worth showing the actual code.
Why the migrations do not run again
WooCommerce is the clearest example because it is the one most people have installed. Its installer stores woocommerce_db_version and runs update callbacks only when that stored version is behind the code.
Here is the guard, from includes/class-wc-install.php on WooCommerce trunk (read 2026-09-09):
foreach ( $updates as $version => $update_callbacks ) {
if ( version_compare( $current_db_version, $version, '>=' ) ) {
continue;
}
And the check that decides whether the updater runs at all:
public static function needs_db_update() {
$current_db_version = get_option( 'woocommerce_db_version', null );
return ! is_null( $current_db_version ) && version_compare(
$current_db_version, array_key_last( self::$db_updates ), '<' );
}
>= means skip. After a rollback, woocommerce_db_version is still at the number the newer release set, so needs_db_update() returns false and every callback is skipped.
There is no branch in that loop for going backwards, and there is no second list of callbacks that undo the first list. This is not a WooCommerce shortcoming. It is how nearly every plugin's migration code is written, because writing a reversible migration is real work that almost nobody is asked to pay for.
So the state you land in after a rollback is old code, new schema, new option values, and a version number that tells the old code everything is already fine.
Deactivation hooks do not run either
The other assumption worth killing is that a rollback gives the plugin a chance to clean up after itself. It does not, and this is in core, not in the plugin.
Plugin_Upgrader hooks deactivate_plugin_before_upgrade onto upgrader_pre_install, and that method calls:
// Deactivate the plugin silently, Prevent deactivation hooks from running.
deactivate_plugins( $plugin, true );
That is core's own comment, in wp-admin/includes/class-plugin-upgrader.php (read 2026-09-09). The second argument is $silent. Anything the plugin registered with register_deactivation_hook is skipped, on the way up and on the way down, because a rollback runs through the same upgrader as an update.
We wrote about the forward version of this problem in testing a plugin's upgrade routine. The short version for rollbacks: no plugin code runs to reverse anything, so nothing is reversed.
Test the rollback the same way you should have tested the update
The routine is the one WP Rollback's own page asks for, made concrete. Do it on a disposable copy of the site that actually has the problem, not on a clean install, because a clean install has none of the data that the downgrade has to survive.
1. Copy the broken site, not a fresh one. Install the SandyWP Cloner on the live site and clone it: production is only read, never written, so cloning a site that is currently misbehaving does not make it worse.
You now have the exact schema, the exact option values and the exact stored version number that the rollback has to cope with.
2. Save a template before you touch anything. A template is a snapshot of the finished sandbox, so you can restore the pre-rollback state and try a different version without cloning again. You will want this, because the first version you pick is often not the one you end up shipping.
3. Turn on debug logging first. Flip WP_DEBUG and WP_DEBUG_LOG in debug mode before the downgrade, not after. The interesting errors happen during the first request after the rollback, and if logging was off you have to do the whole thing again to see them.
4. Note the stored version number before and after. For WooCommerce that is wp option get woocommerce_db_version, and most plugins store something similar under a name containing version or db_version.
Run it before the rollback, run it after, and expect it to be unchanged. That unchanged number is the whole problem in one line of output.
5. Roll back, then exercise the data, not the dashboard. A plugins screen with no red banner proves almost nothing.
Load the records the newer version created: the most recent orders, the newest form entries, the last few posts saved in the new editor. The rows written before the update will read fine and tell you nothing.
6. Read the log, then throw the sandbox away. If it is clean, you have a rollback you can run on production with a known result. If it is not, you have found the outage on a copy, which is the entire point.
The upgrade testing workflow is the same shape as this one, run in the other direction. If you have that habit already, this is not new work.
When not to roll back at all
Sometimes the honest answer is that rolling back is the more dangerous move, and it is worth saying so before you reach for it.
If the newer version migrated data into a format only it understands, going back can lose the migrated data rather than restore the old state. Custom table migrations and order-storage changes are the usual culprits.
If the update fixed a security issue, a rollback reopens it. A broken feature is better than a public vulnerability for the few hours a fix takes.
And if the plugin is not on wordpress.org, the free WP Rollback cannot touch it at all: the plugin page is explicit that it "only works with plugins or themes hosted on the WordPress.org repository". For premium plugins you are downloading the old zip from the vendor by hand, which means you need a copy of that zip before you need it.
In those three cases, fixing forward on a copy is usually faster than arguing with the database. Reproduce it in a sandbox, find the actual breakage, and either patch it or wait for the point release.
What a sandbox will not tell you
A copy will not reproduce load. If the newer version broke under concurrency and the rollback fixes it, you will not see either effect on a sandbox with one visitor.
It will not reproduce your production gateway, your CDN or your real DNS. Anything that depends on those needs a different test, and our post on testing a WooCommerce checkout covers why a passing test order on a copy proves less than it looks like it does.
And it will not tell you whether the old version is safe to stay on. It tells you whether the downgrade itself is survivable, which is the question you have at 2am and the one nothing else answers.
The short version
A rollback replaces files. It does not reverse schema changes, does not reset the stored database version, does not run deactivation hooks, and does not migrate data back.
Core's rollback only helps when the update process failed, and it deletes its backup when the update succeeds, which is exactly the case that hurts.
So test the downgrade on a copy of the broken site, check the stored version number before and after, and exercise the newest data rather than the dashboard. That takes about ten minutes and it is the difference between one outage and two.
