A client came to us with a WooCommerce shop selling specialized computer parts. Their old developer had piled on WooCommerce custom fields with a generic plugin, and it was a mess. The product admin pages were timing out, and the main shop filters for things like ‘RAM Type’ or ‘CPU Socket’ were useless. They had thousands of products and no real way to sort through them. Their next move was to build an AI assistant to help users find compatible parts, but with the data structured this way, that was a non-starter.
It’s a classic case of seeing every problem as a nail because the only tool you have is a hammer, and here that hammer is the wp_postmeta table. Shoving everything into post meta is easy, and it’s the default for most custom field plugins. But for data that needs to be filtered, queried, and sorted at scale, it’s a performance disaster. It’s one of the most common problems we get called in to fix.
Why post meta fails for filtering
My first thought was to build a complex WP_Query with a giant meta_query array. And sure, that worked fine on my local machine with ten products. But I’ve been down that road, and it always ends in tears. A meta_query joining against thousands of rows is one of the quickest ways to bring a database server to its knees. It doesn’t scale. You can’t index those values properly, so every query turns into a full table scan.
The original developer had the right intention but the wrong architecture. It reminds me of a post on carlalexander.ca about thinking through the “ingredients” of a product before you start cooking. The fix here isn’t a bigger, messier query. It’s structuring the data correctly from the start.
For data points shared across many products, like a “Socket Type”, the answer is a custom taxonomy. Taxonomies are built for this. They’re indexed, they’re fast, and they plug straight into the filtering widgets and APIs in WordPress and WooCommerce. And it’s not even hard to set up.
/**
* Register a custom taxonomy for CPU Socket Type for products.
*/
function bbioon_register_socket_type_taxonomy() {
$labels = array(
'name' => _x( 'Socket Types', 'taxonomy general name', 'your-text-domain' ),
'singular_name' => _x( 'Socket Type', 'taxonomy singular name', 'your-text-domain' ),
'search_items' => __( 'Search Socket Types', 'your-text-domain' ),
'all_items' => __( 'All Socket Types', 'your-text-domain' ),
'parent_item' => __( 'Parent Socket Type', 'your-text-domain' ),
'parent_item_colon' => __( 'Parent Socket Type:', 'your-text-domain' ),
'edit_item' => __( 'Edit Socket Type', 'your-text-domain' ),
'update_item' => __( 'Update Socket Type', 'your-text-domain' ),
'add_new_item' => __( 'Add New Socket Type', 'your-text-domain' ),
'new_item_name' => __( 'New Socket Type Name', 'your-text-domain' ),
'menu_name' => __( 'Socket Types', 'your-text-domain' ),
);
$args = array(
'hierarchical' => true, // or false for tag-like behavior
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'socket-type' ),
'show_in_rest' => true, // Exposes this to the REST API
);
register_taxonomy( 'socket_type', array( 'product' ), $args );
}
add_action( 'init', 'bbioon_register_socket_type_taxonomy', 0 );
So, what’s the point?
Jumping straight to code without a plan feels fast, but you end up building a house of cards. The real work of a senior developer isn’t writing code, it’s architecting the system so it doesn’t fall over in six months. Choosing a custom taxonomy over a simple post meta field looks like extra work up front, but it’s the difference between a fast e-commerce store that scales and one that falls apart under load.
- For shared, filterable data: Use a custom taxonomy.
- For simple, unique data: Post meta is fine.
- For complex, relational data: A dedicated custom table is your best bet.
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.