Disabling WordPress plugins for one browser only, on a live site
"Deactivate all plugins" is the first step in almost every WordPress troubleshooting guide. On a live store it is also the step that breaks checkout for every real customer while you test. There is another way: turn plu
"Deactivate all plugins" is the first step in almost every WordPress troubleshooting guide. On a live store it is also the step that breaks checkout for every real customer while you test.
There is another way: turn plugins off only for your own browser. Visitors, search engines and other admins keep seeing the normal site. Nothing in the database changes.
This post shows how that works, with a small must-use plugin you can read in five minutes, and the one trap that can quietly wipe a site's active plugin list.
Where WordPress decides which plugins load
On every request, WordPress reads the active_plugins option and loads each plugin in that list. That read goes through get_option(), and every get_option() call runs the filter option_{$name}.
So if code runs before normal plugins load, it can change the list for this one request:
add_filter( 'option_active_plugins', function ( $plugins ) {
return array_diff( $plugins, array( 'some-plugin/some-plugin.php' ) );
} );
Must-use plugins (files in wp-content/mu-plugins/) load before normal plugins, so they are the right place for this filter.
On multisite, network-activated plugins live in a different option, active_sitewide_plugins, filtered with site_option_active_sitewide_plugins. Handle both if you support multisite.
Scoping it to one browser
The filter above applies to everyone. We only want it for one person.
The obvious idea is "only when the current user is an admin". That does not work here: when must-use plugins run, WordPress has not loaded the user functions yet (pluggable.php loads after plugins). You cannot call is_user_logged_in() this early.
So use a cookie instead:
- When the admin starts a test session (from a normal admin page, where you can check capabilities and a nonce), create a random token.
- Store a hash of the token, an expiry time and the list of plugins to skip in an option.
- Send the raw token to the admin's browser as a cookie.
- In the must-use plugin, compare the cookie's hash to the stored hash. Only filter when they match.
<?php
/**
* Plugin Name: One-browser plugin filter (demo)
* Description: Skips selected plugins for one browser session only.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
const OBPF_COOKIE = 'obpf_session';
const OBPF_OPTION = 'obpf_session';
function obpf_current_session() {
static $session = null;
static $checked = false;
if ( $checked ) {
return $session;
}
$checked = true;
if ( empty( $_COOKIE[ OBPF_COOKIE ] ) ) {
return null;
}
// Shape: [ 'token_hash' => string, 'expires' => int, 'disabled' => string[] ]
$stored = get_option( OBPF_OPTION );
if ( ! is_array( $stored ) || time() > (int) $stored['expires'] ) {
return null;
}
$token = (string) wp_unslash( $_COOKIE[ OBPF_COOKIE ] );
if ( ! hash_equals( $stored['token_hash'], hash( 'sha256', $token ) ) ) {
return null;
}
$session = $stored;
return $session;
}
add_filter( 'option_active_plugins', function ( $plugins ) {
$session = obpf_current_session();
if ( ! $session ) {
return $plugins;
}
return array_values( array_diff( (array) $plugins, (array) $session['disabled'] ) );
} );
Why a hash and hash_equals()? If the database leaks, the stored value alone is not enough to start someone else's session, and the comparison does not leak timing information.
Anyone without the cookie gets the unfiltered list. That includes visitors, crawlers, and other admins on their own browsers.
The trap: saving the filtered list
This is the part that bites people.
While your session is active, get_option( 'active_plugins' ) returns the shortened list. Now imagine you activate or deactivate any plugin during the session. WordPress reads the active list (shortened), changes it, and saves it back with update_option().
The plugins you were "only hiding" are now really deactivated, for everyone.
Ways to avoid it:
- Block plugin changes during a session. Disable activate/deactivate/update/delete actions on the Plugins screen while the cookie is valid, and show a notice that says why.
-
Or don't filter on those requests. Skip the filter on
plugins.phpandupdate.phpactions. This is harder to get fully right, because other code can also write the option. -
Test it. Start a session, try to activate a plugin, then check the real option from a second browser or with WP-CLI:
wp option get active_plugins.
Caching
Page caches are the other place this can go wrong.
- The admin is logged in, and most page caches skip logged-in users, so the admin usually sees uncached pages. Check your cache does this, or add the session cookie to its bypass list.
- The bigger risk is the reverse: a filtered page (missing a plugin's output) getting cached and served to visitors. A cookie bypass rule prevents this.
- The persistent object cache is safe. The filter runs after the value is read, so the cached
active_pluginsvalue itself is never changed.
Safety exits
If you skip the wrong plugin, you can lock yourself out of wp-admin. For example, a security or login plugin, or a plugin that another plugin depends on. You need ways out that do not depend on the admin area working:
- A clear Exit button in the admin bar that deletes the option and the cookie.
- An emergency link that works when you are logged out. Because the must-use plugin runs before authentication, it can check for a secret URL parameter and end the session. Show the link to the admin when the session starts, so they can copy it.
- An expiry time. Sessions should end by themselves. Pair a short idle timeout with a hard maximum.
- Last resort: delete the session option with WP-CLI or phpMyAdmin, or rename the must-use plugin file over FTP.
What this method can't test
Because it works by filtering the plugin list, some things stay out of reach:
- The theme. It loads through a different path.
-
Must-use plugins and drop-ins (
object-cache.php,advanced-cache.php,db.php). They load before or outside the normal plugin list. -
Server settings: PHP version, memory limit,
.htaccessor nginx rules.
If a problem survives with every normal plugin skipped, look at those next.
Tools that do this for you
You don't have to write this yourself:
- Health Check & Troubleshooting: its troubleshooting mode turns off plugins for your session only. You choose which ones to turn back on.
- Culprit Finder: a free, GPL plugin I built (disclosure: I'm the author). It switches plugins off only for your own logged-in browser and never changes the active plugin list. It then runs a binary search to find the plugin causing the problem, or the two plugins that only break together. It has an Exit button, an emergency link that works logged out, and it stops by itself after 60 idle minutes (3 hours max). No account, no tracking, no external requests.
Summary
-
option_active_pluginsin a must-use plugin lets you change the plugin list for one request. - Scope it with a hashed cookie token, not a user check. User functions aren't loaded yet.
- Never let the filtered list be saved back. Block plugin changes during a session.
- Make sure page caches never store or serve the filtered version.
- Build exits that work even when wp-admin doesn't.
Have you used a per-session approach on a production site? I'd like to hear what broke, or what you'd add to the safety list.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.