When Good Plugins Go Bad: A Guide to WordPress Hooks

Got a call from a client running a decent-sized WooCommerce shop. They had a custom plugin we built that offered free shipping for VIP members. Simple enough. It worked for years. Then, they installed a new table-rate shipping plugin to handle complex international orders. And just like that, our VIP free shipping was gone. The new plugin was stomping all over our logic. Total mess.

This is a classic case of plugin warfare, and it almost always comes down to a misunderstanding of the WordPress Plugin API. It’s the system of hooks—actions and filters—that lets developers modify how WordPress and other plugins behave. When two plugins try to change the same thing, like a shipping rate, you get chaos unless you know how to direct traffic.

The Real Problem: WordPress Hook Priority

When a developer wants to change something, they use add_filter(). Think of it as getting in line to inspect or change a piece of data before it’s used. The problem is, most plugins get in the default line, which is priority 10. When everyone has the same priority, WordPress just runs them in the order they were loaded. It’s unpredictable.

My first thought was the junior dev fix: just make my plugin run last. Force it. I could set my filter’s priority to 999. And yeah, that would probably “fix” it by overwriting whatever the other plugin did. But that’s a hack. What if the client wanted to use the table-rate plugin to add a special handling fee, and then my plugin should make it free? My “fix” would just wipe out the handling fee. Not good. The real solution is to make the plugins cooperate.

// The "brute force" way -- just run last
add_filter( 'woocommerce_package_rates', 'vip_shipping_fix', 999 );

// The RIGHT way -- run after the default, and modify intelligently
function smart_vip_shipping_fix( $rates ) {
    // Check if a VIP user is logged in
    if ( is_user_logged_in() && current_user_can('vip_member') ) {
        // Instead of replacing the rates, find "free shipping" if it exists
        // Or unset other rates, leaving only the free one.
        // This is much safer than just returning a whole new array.
        foreach ( $rates as $rate_id => $rate ) {
            if ( 'free_shipping' !== $rate->method_id ) {
                unset( $rates[ $rate_id ] );
            }
        }
    }
    return $rates;
}
add_filter( 'woocommerce_package_rates', 'smart_vip_shipping_fix', 20 );

So, What’s the Real Takeaway Here?

It’s not about forcing your code to run first or last. It’s about understanding the execution flow and writing defensive code that works *with* other plugins. You hook in, you inspect the data that’s handed to you, and you modify it carefully. You don’t just throw out everything that came before. The WordPress Plugin API is incredibly powerful, but you have to be surgical. For a much deeper, more technical look under the hood, a post I read over at carlalexander.ca is a great resource.

Look, this stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.

Leave a Reply

Your email address will not be published. Required fields are marked *