← Back to blog

How to test for a WordPress plugin conflict without getting a false result

The SandyWP team 5 min read

To find a plugin conflict, deactivate everything, confirm the problem is gone, then reactivate in halves until it comes back. That part is not hard, and every guide on the subject describes it.

The part nobody writes about is why the result is often wrong. Deactivating a plugin does not undo what it did, so "the bug went away" and "that plugin caused the bug" are not the same statement.

The bisect itself, in the fastest form

Binary search, not one at a time. With 24 plugins that is five rounds instead of an average of twelve.

If you have shell access, do it with WP-CLI so each round is one command and the state is exact:

wp plugin list --status=active --field=name > /tmp/all.txt
wp plugin deactivate --all
# reproduce: is the problem gone?
wp plugin activate $(head -12 /tmp/all.txt)

Reproduce again. If the problem is back, the culprit is in that twelve. If not, it is in the other twelve. Halve and repeat.

Record the list at every round. Four rounds in, "I think I had Yoast on" is how people end up starting over.

Where the standard method gives you a false result

1. Deactivation is not an undo

A plugin writes rows to wp_options, adds terms, registers post types, rewrites .htaccess, drops a must-use file, and creates its own tables. Deactivating it leaves nearly all of that in place.

So a plugin can be the cause of a state that now breaks your site, while switching it off changes nothing. The bisect clears it, and you go looking in the wrong place.

The signature of this case: the problem persists with all plugins off. If that happens, stop bisecting. You are looking at leftover data, a theme, a must-use plugin or a drop-in, not an active-code conflict.

wp plugin list --status=must-use,dropin

Must-use plugins and drop-ins such as object-cache.php and advanced-cache.php do not appear in the normal list and cannot be deactivated from the admin. Hosts install them. WooCommerce's own conflict guide flags this and tells you to ask the host, which is the honest answer on live hosting.

2. Troubleshooting mode only tests your own session

The Health Check plugin's troubleshooting mode is genuinely good, and it is the right first move on a live site because visitors keep seeing the working version. But it disables plugins for your logged-in session only.

That means it cannot see any bug that only happens to logged-out visitors, and a lot of them only happen to logged-out visitors, because that is who gets served the page cache. Test the front end in a private window with an account that is not yours, or you are testing a different site from the one your users are on.

3. The cache answers before the code does

A full-page cache, a persistent object cache, and transients each hold a copy of the old result. Clear the browser cache and you have cleared none of them.

Between rounds:

wp cache flush
wp transient delete --all
wp rewrite flush

If you skip this, the bisect measures cache expiry rather than plugin behaviour, and the answer moves every time you run it.

4. It is load order, not a pair of plugins

Plenty of "conflicts" are two plugins hooking the same filter, and the one that runs last wins. Deactivate either and the symptom goes, which makes both look guilty and neither look wrong.

The useful question is not which plugin, but which hook. Turn on WP_DEBUG and WP_DEBUG_LOG, then look at what is attached:

wp eval 'global $wp_filter; print_r(array_keys($wp_filter["the_content"]->callbacks));'

The keys are the priorities. If the fix is a priority number, no amount of activating and deactivating was ever going to point at it.

Why a disposable copy beats troubleshooting mode

Everything above is easier when you are allowed to break the site. On production you are not, so you test cautiously, which is the same as testing incompletely.

On a throwaway copy you can deactivate all plugins for everyone, wipe the object cache, flush rewrites, leave WP_DEBUG on with errors on screen, and reproduce as a logged-out visitor with no consequences.

The second thing a copy gives you is the one that matters more: every round starts from the same state. Bisecting on a live site is a sequence of one-way changes to a system that never returns to where it began, and by round four you are no longer testing what you think you are.

Our Cloner plugin copies a site you control into a sandbox, and Reset to a clean slate returns that sandbox to a clean WordPress install when a round of testing has left a mess. Save the pre-bisect state as a Template first and you can relaunch that exact starting point as many times as the bisect takes.

For the actual work, SSH access gives you WP-CLI inside the sandbox, so the commands above run as written rather than through a file manager.

When the copy will not reproduce it

Be honest about this before you spend an hour on it. A sandbox is not your host.

It will not reproduce a conflict that depends on PHP memory limits, a specific PHP extension your host compiled in, max_input_vars, a server-level cache, a CDN rule, a firewall, or a host-installed must-use plugin. Those are hosting-layer problems wearing a plugin costume.

It also will not reproduce anything that depends on live traffic volume, a real payment gateway in live mode, or data your copy does not contain.

If the bug survives a clean bisect on a faithful copy, the conflict is probably not between two plugins. That is a real result, not a failed test, and it is usually the point where the host or the plugin author has the information you do not.

A checklist that fits on one screen

  1. Reproduce it once, reliably, and write down the exact steps. An intermittent bug bisects to a random plugin.
  2. Clone the site to a sandbox rather than testing on production.
  3. Check must-use plugins and drop-ins before you touch the plugin list.
  4. Deactivate all. If the problem survives, it is not an active-plugin conflict.
  5. Bisect in halves, clearing caches between rounds.
  6. Confirm the finding twice: off and working, on and broken.
  7. Look at hook priorities before you report it as a conflict between two plugins.

Step 6 is the one people skip, and it is the cheapest. A conflict you have only confirmed in one direction is a coincidence you have not caught yet.

If you want somewhere to run this that you can throw away afterwards, create a sandbox and clone the site into it.