How to Debug a WordPress Plugin Conflict Without Taking the Site Down

How to Debug a WordPress Plugin Conflict Without Taking the Site Down

A WordPress plugin conflict is two or more plugins, or a plugin and the active theme, competing for the same hook, filter, script handle or request lifecycle stage, and the fastest way to find the culprit is to remove suspects from the load path one at a time rather than deactivating everything at once. You can do that on a live site without visitors seeing a broken page by controlling which plugins load for your session only, using a query parameter, a must-use filter, or a staging copy that shares the production database.

The instinct to open the plugins screen and click Deactivate on half the list is the one that hurts. Every deactivation writes to the active_plugins option and fires deactivate_plugin hooks, plugin code often drops tables or clears caches on deactivation, and for the duration of your bisect every visitor gets the reduced plugin set. A theme or plugin that registers a custom post type, a shortcode or a REST route will take the front end down with it. The goal is to change the load set for one request, not for the whole site.

Understand what actually loads on a request

WordPress reads the active_plugins row from the wp_options table, merges it with any must-use plugins in wp-content/mu-plugins and network-activated plugins on multisite, then includes each file in order. Order matters because a plugin that runs later can override a filter registered earlier. The option_active_plugins filter fires while that option is read, which is the hook you want: it lets you rewrite the list per request without touching the database.

Two other filters sit in the same neighborhood. option_active_plugins changes which plugins load. The plugins_loaded action fires after they are all in. If you filter the option early enough, you can shape the entire request. A must-use plugin loads before regular plugins and cannot be deactivated from the admin, which makes it the right place to put the switch.

Isolate a wordpress plugin conflict with a query parameter

Drop a file into wp-content/mu-plugins/ called something like conflict-switch.php. Must-use plugins are loaded automatically, no activation step. The file reads a query parameter and, if present and valid, filters the active plugin list down to the plugins you name. Visitors never send that parameter, so they always get the normal set.

<?php
// wp-content/mu-plugins/conflict-switch.php
add_filter( 'option_active_plugins', function ( $plugins ) {
    if ( empty( $_GET['debug_plugins'] ) ) {
        return $plugins; // untouched for normal visitors
    }
    $allowed = array_filter( array_map( 'sanitize_text_field',
        explode( ',', $_GET['debug_plugins'] ) ) );
    return array_values( array_intersect( $plugins, $allowed ) );
} );

Now request the broken page with only the plugins you want to keep:

curl -sI "https://example.com/checkout/?debug_plugins=woocommerce,my-gateway" | head -n 5
HTTP/2 200
content-type: text/html; charset=UTF-8
x-cache: MISS

If the page renders correctly with two plugins and breaks when you add a third, the third is your suspect. If it breaks with an empty list, the theme or core is the problem, not a plugin. This is a bisect: halve the set, test, halve again. Each request is independent, so you can run the tests from a terminal in seconds and no visitor is affected.

The parameter is a backdoor, so guard it. Restrict the filter to a logged-in user with a capability check, or to a specific IP, or require a secret value that only you know. Never leave an unauthenticated parameter that lets anyone disable arbitrary plugins. If the site has a full-page cache in front of PHP, the cached response will not reflect your filtered request; append a cache-busting query string or test against the origin directly.

When the admin itself is unreachable

If the conflict is bad enough that wp-admin white-screens or redirects in a loop, you cannot click anything. Use the same filter but key it off a cookie or a header instead of a GET parameter, then set that header in your browser or with curl. Alternatively, rename the plugin directory over SSH to remove it from disk entirely, which is reversible in one command:

cd wp-content/plugins
mv suspect-plugin suspect-plugin.off
# test the site
mv suspect-plugin.off suspect-plugin

WordPress will silently skip a plugin whose folder is missing from active_plugins and may print a notice. That is fine for a few minutes of testing. Do not leave it that way; the option still lists the plugin and the admin will complain until you restore the name.

Read the evidence instead of guessing

Before you bisect, turn on logging so you know what to look for. In wp-config.php set WP_DEBUG and WP_DEBUG_LOG to true and WP_DEBUG_DISPLAY to false, so errors go to wp-content/debug.log rather than into the page for visitors. A fatal error names the file and line that threw it, which often points straight at the offending plugin. A PHP notice about an undefined function usually means a plugin expects another one that is not loaded. A JavaScript error in the browser console with a path under wp-content/plugins/ tells you two plugins are fighting over the same script handle or enqueue order.

Check the response headers too. A plugin that starts a session or sends output early will trigger Cannot modify header information warnings, and the Set-Cookie or X-Powered-By headers can reveal which component is running. If the page is cached, a stale object cache entry can survive a plugin change; flush it before you conclude anything. Two plugins hooking the same filter at the same priority run in registration order, so a conflict can appear or vanish when an unrelated plugin is added or removed, which is why you change one variable per request.

Make the test repeatable and then clean up

Keep a written record of each request you sent and what happened. Without it, bisecting a large plugin set turns into guesswork. Once you have the pair, look at both plugins' source for the same hook name, the same option key or the same script handle, then decide which one to keep, update, or replace. If neither can be dropped, a small must-use plugin that unhooks one of them at the right priority is a legitimate fix, but document it, because it will confuse the next person who reads the site.

When you are done, delete the switch file from wp-content/mu-plugins/ and confirm the site loads normally without the query parameter. Then revisit the plugin list with fresh eyes: fewer plugins means fewer future conflicts, and the ones you keep should be the ones you actually use. If you cannot reproduce the conflict on a staging copy, say so in your notes, because that usually means the production site has a cache, an object store or a configuration difference that is part of the bug.

Related articles

Subscribe to our newsletter

Get the latest hosting tips, performance insights, and industry news.