WooCommerce product types: practical tips and trade-offs. Simple, variable, grouped and custom.
Variable products for sizes and colors
Variable products are the workhorse of most WooCommerce stores. They let customers pick from different options—think t-shirt sizes, paint colors, or shoe widths—all linked to a single product page with its own SKU, price, and stock level per variation.
When you create a variable product, you first define attributes (like "Size" or "Color"), then create variations that combine those attributes. Each variation can have its own price modifier, stock quantity, and image gallery. This approach scales well for 5–50 variations, though we've seen stores push beyond that without major performance issues.
Here's how to programmatically create a variable product with variations:
$product = new WC_Product_Variable();
$product->set_name('Premium T-Shirt');
$product->set_regular_price(29.99);
$product->save();
// Create size attribute
$size_attr = new WP_Term_Query([
'taxonomy' => 'pa_size',
'fields' => 'ids',
]);
if (empty($size_attr->terms)) {
wp_insert_term('Small', 'pa_size');
wp_insert_term('Medium', 'pa_size');
wp_insert_term('Large', 'pa_size');
}
// Link attribute to product
$product->set_attributes([
'pa_size' => [
'name' => 'pa_size',
'options' => ['Small', 'Medium', 'Large'],
'visible' => true,
'variation' => true,
]
]);
$product->save();
// Create a variation
$variation = new WC_Product_Variation();
$variation->set_parent_id($product->get_id());
$variation->set_attributes(['attribute_pa_size' => 'small']);
$variation->set_regular_price(29.99);
$variation->set_stock_quantity(50);
$variation->set_manage_stock(true);
$variation->save();The key advantage: customers see one product listing, but inventory and pricing are granular. The trade-off is complexity—you're managing multiple entities in the database, and filters on the shop page need careful optimization to avoid N+1 queries.
Simple and grouped products: when to use them
Simple products are your baseline. No options, no variations—just one SKU, one price, one stock count. Use them for items that genuinely don't vary: a book, a license key, a digital download, or a service that's always the same.
Grouped products link multiple simple products together under one parent. A classic example: a "Starter Kit" that contains a brush, paint, and primer, each buyable separately but marketed as a bundle. The parent product itself doesn't have inventory; customers add children to the cart individually.
Grouped products are less common but invaluable for:
- Accessory bundles where you want to push component sales
- Related products (e.g., "All colors of this throw pillow")
- Seasonal kits or collections
The trade-off: grouped products are static. You can't apply automatic discounts to the bundle, and cart logic doesn't treat them as a single unit. For complex bundling, consider a dedicated plugin or a custom product type.
Custom product types for special cases
WooCommerce's built-in types cover 80% of use cases, but sometimes you need something bespoke. We've built custom product types for rental systems, subscription boxes, and B2B wholesale orders with minimum quantity logic.
A custom product type inherits from WC_Product and lets you override pricing, availability, cart handling, and admin interfaces. Here's a skeleton:
class WC_Product_Rental extends WC_Product {
public function __construct($product = 0) {
$this->product_type = 'rental';
parent::__construct($product);
}
public function get_type() {
return 'rental';
}
public function get_rental_duration() {
return get_post_meta($this->get_id(), '_rental_duration', true);
}
public function set_rental_duration($duration) {
update_post_meta($this->get_id(), '_rental_duration', $duration);
return $this;
}
}
// Register the type
add_filter('woocommerce_product_class', function($product_class, $product_type, $product_id) {
if ($product_type === 'rental') {
return 'WC_Product_Rental';
}
return $product_class;
}, 10, 3);You'll also need to register it with WooCommerce:
add_filter('product_type_selector', function($types) {
$types['rental'] = 'Rental';
return $types;
});Custom types shine when the product logic is genuinely different from the standard types. But weigh the cost: you're adding complexity, increasing test coverage needs, and potentially breaking compatibility with plugins that hook into product types they don't recognize.
Use WC_Product CRUD methods, not direct post meta
This is the most critical practice we see teams underestimate. The WC_Product class exposes CRUD methods—getters and setters—for all standard product data. Use them. Don't reach into the database and update post meta directly.
Why? CRUD methods trigger hooks, rebuild caches, validate data, and maintain consistency. Direct post meta queries bypass all that:
// Bad
update_post_meta($product_id, '_regular_price', 49.99);
update_post_meta($product_id, '_stock', 100);
// Good
$product = wc_get_product($product_id);
$product->set_regular_price(49.99);
$product->set_stock_quantity(100);
$product->save();The good approach triggers woocommerce_product_object_updated_props, clears the product cache, and fires any dependent hooks (like recalculating cart totals if the product is already in someone's cart).
If you're bulk-updating many products, use wc_get_products() with batch processing rather than looping through raw queries:
$products = wc_get_products([
'limit' => 100,
'offset' => 0,
'status' => 'publish',
]);
foreach ($products as $product) {
if ($product->get_regular_price() > 100) {
$product->set_regular_price($product->get_regular_price() * 0.9);
$product->save();
}
}Avoid direct post meta access
Building on that: don't use get_post_meta() or update_post_meta() for product fields that have CRUD methods. It's tempting when you're in a hurry—one line versus three—but you're creating technical debt.
Cases where direct meta access is okay:
- Custom fields that aren't part of WooCommerce's schema (e.g.,
_my_custom_supplier_id) - One-off migrations or data cleanup scripts
- High-volume analytics reads where you're only querying, not writing
For everything else, use the CRUD layer. This means when WooCommerce updates or you write custom code, all your product modifications stay consistent.
Common mistakes
Overusing variable products. If you have 200+ variations, you'll hit performance walls in the admin and on the front end. Consider splitting into multiple products or a custom type with better filtering logic.
Mixing product types carelessly. We've seen stores create grouped products of variable products, then wonder why cart logic breaks. Each type has constraints; respect them.
Updating stock via direct meta. Stock is sacred. If you modify _stock without using set_stock_quantity(), you'll have inconsistent data, broken low-stock alerts, and out-of-sync inventory reports. Always use CRUD methods for stock.
Building custom types for cosmetic differences. If the only difference is a template or an extra field or two, use attributes, meta, or a hook instead. Custom types are for behavioral changes.
Key takeaways
- Variable products scale well for 5–50 variations per product; beyond that, profile carefully and consider custom types.
- Simple and grouped products are lighter weight but less flexible; use them when the product genuinely doesn't vary or when you want static bundling.
- Custom product types are powerful for rental, subscription, or special-case logic, but add development and maintenance overhead.
- Always use
WC_ProductCRUD methods (getters, setters,save()) instead of reaching into post meta directly. - Stock updates must use
set_stock_quantity()to ensure consistency across caches, reports, and hooks. - Test product type behavior across your entire plugin ecosystem—caching layers, payment gateways, and inventory sync tools often have assumptions about which types they support.






Comments
Loading comments…