A new client called a few weeks back, frustrated. Their WooCommerce dashboard was timing out constantly, and just loading the main admin screen took ages. They were convinced they needed a bigger server, but a quick look told me the problem wasn’t the hardware. It was a custom dashboard widget, meant to show low-stock products, that was dragging the whole system down. It is a classic case of ignoring the pragmatic option in WordPress development for something far more complex, and far worse.
The previous developer, who was clearly smart, had built an entire custom framework for this one widget: custom database tables, a bespoke REST API endpoint to serve the data, and a slew of classes trying to follow SOLID principles to the letter. It was the kind of thing you would read about in a software architecture textbook, an idea I have seen discussed on sites like Carl Alexander’s blog. In a WordPress context, though, it was a mess. A solution in search of a problem.
My first commit was a mistake
My first instinct was to try and fix it, and that was my mistake. I dove into their code thinking, “I’ll just optimize the queries on their custom tables, maybe add some caching to their API endpoint.” I spent a solid two hours trying to untangle the mess before I admitted it was a fool’s errand. The entire foundation was wrong, so every line I wrote just added complexity to an already bloated system. It was premature optimization creating more problems than it solved.
Often the best code you can write is the code you delete. Rather than fix the over-engineered mess, the pragmatic move was to delete it entirely.
Replacing 1,000 lines with 10
So that is what I did. I deleted the custom tables, the custom endpoint, all of it, then replaced the whole thing with a simple function that hooks into the WordPress dashboard and calls a built-in WooCommerce function. It leans on the core systems WordPress and WooCommerce already provide, which is almost always the right call.
add_action('wp_dashboard_setup', 'add_low_stock_dashboard_widget');
function add_low_stock_dashboard_widget() {
wp_add_dashboard_widget(
'low_stock_products_widget',
'Products Low on Stock',
'display_low_stock_products'
);
}
function display_low_stock_products() {
$args = array(
'post_type' => 'product',
'posts_per_page' => 10,
'meta_query' => array(
'relation' => 'AND',
array(
'key' => '_manage_stock',
'value' => 'yes',
),
array(
'key' => '_stock',
'value' => 5, // Or your low stock threshold
'compare' => '<=',
'type' => 'NUMERIC',
),
),
);
$low_stock_query = new WP_Query($args);
if ($low_stock_query->have_posts()) {
echo '<ul>';
while ($low_stock_query->have_posts()) {
$low_stock_query->the_post();
echo '<li><a href="' . get_edit_post_link() . '">' . get_the_title() . ' (' . get_post_meta(get_the_ID(), '_stock', true) . ' left)</a></li>';
}
echo '</ul>';
wp_reset_postdata();
} else {
echo 'All products are sufficiently stocked.';
}
}
So, what’s the takeaway?
The lesson isn’t that design patterns are bad. It is that context matters. WordPress has its own way of doing things, its own APIs, and its own performance quirks, and fighting the framework instead of using it usually ends badly. The best solution is often the simplest one that uses the tools you already have. Don’t build a tractor when you just need to turn a screw.
This stuff gets complicated fast. If you are tired of debugging someone else’s mess and you just want your site to work, drop my team a line. We have probably seen it before.