Your WooCommerce store has variable products with multiple variations, but customers have to click into each product to see the different options. This creates extra steps that can hurt your conversion rates.
The solution: Display product variations as individual products directly on your shop page, making it easier for customers to find and purchase exactly what they want.
Here’s how to implement this functionality using the WpCode plugin with three different approaches.
Understanding the Problem
When you have a variable product like “T-shirt” with size variations (S, M, L), WooCommerce typically shows only the main “T-shirt” product on your shop page. Customers must click through to see the size options.
By displaying variations as individual products, you can show “T-shirt S”, “T-shirt M”, and “T-shirt L” directly on the shop page, improving user experience and potentially increasing sales.
How to Display WooCommerce Variations as Individual Products?
Here’s an overview and tutorial video.
Version 1: Show Main Product + Variations
This version displays both the main variable product and its variations on your shop page.
What it does: If you have a “T-shirt” with variations S, M, L, your shop will show:
- T-shirt (main product)
- T-shirt S
- T-shirt M
- T-shirt L
- Install WpCode Plugin
- Go to Plugins → Add New
- Search for “WpCode”
- Install and activate the plugin
- Add the Code Snippet
- Navigate to Code Snippets → Add Snippet
- Choose “Add Your Custom Code (New Snippet)”
- Select “PHP Snippet” as the code type
- Give it a title like “Display Variations with Main Product”
- Paste the Code Copy the entire contents of this code here below into the code editor.
- Set “Auto Insert” to “Run Everywhere”
- Save and activate the snippet
/**
* Display Variations as Single Products - Version 1
*
* This version displays variations along with the main variable product.
* Add this code to your theme's functions.php file.
*
* Example: If you have a variable product "T-shirt" with variations S, M, L,
* the shop page will display: "T-shirt", "T-shirt S", "T-shirt M", "T-shirt L"
*
* @version 1.0
* @author WP Simple Hacks
* @website https://wpsimplehacks.com
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Check if we should modify this query
*
* @param WP_Query $query Query object
* @return bool Whether to modify the query
*/
function sv_v1_should_modify_query($query): bool {
// Don't modify admin queries
if (is_admin()) {
return false;
}
// Don't modify AJAX requests (like price filters)
if (wp_doing_ajax()) {
return false;
}
// Don't modify WooCommerce internal queries - sanitize input
$min_price = sanitize_text_field($_GET['min_price'] ?? '');
$max_price = sanitize_text_field($_GET['max_price'] ?? '');
$price_filter = sanitize_text_field($_GET['price_filter'] ?? '');
if (!empty($min_price) || !empty($max_price) || !empty($price_filter)) {
return false;
}
// Don't modify Blocksy theme filter requests - sanitize input
$blocksy_filter = sanitize_text_field($_GET['blocksy_filter'] ?? '');
$ct_filter = sanitize_text_field($_GET['ct_filter'] ?? '');
if (!empty($blocksy_filter) || !empty($ct_filter)) {
return false;
}
// Don't modify queries that are not main shop queries
if (!$query->is_main_query()) {
return false;
}
// Only modify on shop page, category pages, and search results
if (!is_shop() && !is_product_category() && !is_product_tag() && !is_search()) {
return false;
}
// Check if this is a product query
$post_types = $query->get('post_type');
if (is_array($post_types)) {
return in_array('product', $post_types, true) || in_array('product_variation', $post_types, true);
} else {
return $post_types === 'product' || $post_types === 'product_variation';
}
}
/**
* Modify product query to include variations
*
* @param WP_Query $query Query object
*/
function sv_v1_modify_product_query($query): void {
// Only modify queries on the frontend
if (is_admin()) {
return;
}
// Always include product variations
$post_types = array('product', 'product_variation');
$query->set('post_type', $post_types);
// Set custom ordering to group variations with parent products
$query->set('orderby', 'parent_group menu_order ID');
$query->set('order', 'ASC');
}
/**
* Custom JOIN to get parent product ID for variations
*
* @param string $join JOIN clause
* @param WP_Query $query Query object
* @return string Modified JOIN clause
*/
function sv_v1_custom_join(string $join, $query): string {
global $wpdb;
// Only apply on frontend product queries
if (!sv_v1_should_modify_query($query)) {
return $join;
}
// Avoid conflicts with existing joins
if (strpos($join, 'parent_meta') !== false) {
return $join;
}
// Use prepared statement for security
$join .= $wpdb->prepare(
" LEFT JOIN {$wpdb->postmeta} AS parent_meta ON ({$wpdb->posts}.ID = parent_meta.post_id AND parent_meta.meta_key = %s)",
'_parent_id'
);
return $join;
}
/**
* Custom FIELDS to add parent grouping
*
* @param string $fields FIELDS clause
* @param WP_Query $query Query object
* @return string Modified FIELDS clause
*/
function sv_v1_custom_fields(string $fields, $query): string {
global $wpdb;
// Only apply on frontend product queries
if (!sv_v1_should_modify_query($query)) {
return $fields;
}
// Avoid conflicts with existing fields
if (strpos($fields, 'parent_group') !== false) {
return $fields;
}
$fields .= ", CASE WHEN parent_meta.meta_value IS NOT NULL THEN parent_meta.meta_value ELSE {$wpdb->posts}.ID END AS parent_group";
return $fields;
}
/**
* Custom ORDER BY to group variations with their parent products
*
* @param string $orderby ORDER BY clause
* @param WP_Query $query Query object
* @return string Modified ORDER BY clause
*/
function sv_v1_custom_orderby(string $orderby, $query): string {
global $wpdb;
// Only apply on frontend product queries
if (!sv_v1_should_modify_query($query)) {
return $orderby;
}
// Avoid conflicts with existing ordering
if (strpos($orderby, 'parent_group') !== false) {
return $orderby;
}
// Group by parent product ID, then by product type (parent first, then variations)
$custom_orderby = "
parent_group ASC,
CASE
WHEN parent_meta.meta_value IS NOT NULL THEN 1
ELSE 0
END ASC,
{$wpdb->posts}.menu_order ASC,
{$wpdb->posts}.ID ASC
";
return $custom_orderby;
}
// Hook into WordPress
add_action('woocommerce_product_query', 'sv_v1_modify_product_query', 25);
add_filter('posts_orderby', 'sv_v1_custom_orderby', 10, 2);
add_filter('posts_join', 'sv_v1_custom_join', 10, 2);
add_filter('posts_fields', 'sv_v1_custom_fields', 10, 2); The key functions include:
sv_v1_modify_product_query()– Includes both products and variationssv_v1_custom_orderby()– Groups variations with their parent products- Security checks to avoid conflicts with filters and AJAX requests
Version 2: Hide Main Product, Show Only Variations
This version hides the main variable product and displays only the variations.
What it does: If you have a “T-shirt” with variations S, M, L, your shop will show only:
- T-shirt S
- T-shirt M
- T-shirt L
Adding Version 2 to WpCode
- Create New Snippet
- Go to Code Snippets → Add Snippet
- Choose “Add Your Custom Code (New Snippet)”
- Select “PHP Snippet”
- Title it “Display Only Variations (Hide Main Product)”
- Activate the Snippet
– Set “Auto Insert” to “Run Everywhere”
– Save and activate
Important: Only use one version at a time. If you want to switch between versions, deactivate the current snippet before activating the other.
/**
* Display Variations as Single Products - Version 2
*
* This version displays only variations and hides the main variable product.
* Add this code to your theme's functions.php file.
*
* Example: If you have a variable product "T-shirt" with variations S, M, L,
* the shop page will display only: "T-shirt S", "T-shirt M", "T-shirt L"
* (the main "T-shirt" product will be hidden)
*
* @version 1.0
* @author WP Simple Hacks
* @website https://wpsimplehacks.com
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Check if we should modify this query
*
* @param WP_Query $query Query object
* @return bool Whether to modify the query
*/
function sv_v2_should_modify_query($query): bool {
// Don't modify admin queries
if (is_admin()) {
return false;
}
// Don't modify AJAX requests (like price filters)
if (wp_doing_ajax()) {
return false;
}
// Don't modify WooCommerce internal queries - sanitize input
$min_price = sanitize_text_field($_GET['min_price'] ?? '');
$max_price = sanitize_text_field($_GET['max_price'] ?? '');
$price_filter = sanitize_text_field($_GET['price_filter'] ?? '');
if (!empty($min_price) || !empty($max_price) || !empty($price_filter)) {
return false;
}
// Don't modify Blocksy theme filter requests - sanitize input
$blocksy_filter = sanitize_text_field($_GET['blocksy_filter'] ?? '');
$ct_filter = sanitize_text_field($_GET['ct_filter'] ?? '');
if (!empty($blocksy_filter) || !empty($ct_filter)) {
return false;
}
// Don't modify queries that are not main shop queries
if (!$query->is_main_query()) {
return false;
}
// Only modify on shop page, category pages, and search results
if (!is_shop() && !is_product_category() && !is_product_tag() && !is_search()) {
return false;
}
// Check if this is a product query
$post_types = $query->get('post_type');
if (is_array($post_types)) {
return in_array('product', $post_types, true) || in_array('product_variation', $post_types, true);
} else {
return $post_types === 'product' || $post_types === 'product_variation';
}
}
/**
* Modify product query to include variations and hide variable products
*
* @param WP_Query $query Query object
*/
function sv_v2_modify_product_query($query): void {
// Only modify queries on the frontend
if (is_admin()) {
return;
}
// Always include product variations
$post_types = array('product', 'product_variation');
$query->set('post_type', $post_types);
// Exclude variable products (hide main products)
$tax_query = $query->get('tax_query');
if (!is_array($tax_query)) {
$tax_query = array();
}
$tax_query[] = array(
'taxonomy' => 'product_type',
'field' => 'slug',
'terms' => 'variable',
'operator' => 'NOT IN',
);
$query->set('tax_query', $tax_query);
// Set custom ordering to group variations with parent products
$query->set('orderby', 'parent_group menu_order ID');
$query->set('order', 'ASC');
}
/**
* Custom JOIN to get parent product ID for variations
*
* @param string $join JOIN clause
* @param WP_Query $query Query object
* @return string Modified JOIN clause
*/
function sv_v2_custom_join(string $join, $query): string {
global $wpdb;
// Only apply on frontend product queries
if (!sv_v2_should_modify_query($query)) {
return $join;
}
// Avoid conflicts with existing joins
if (strpos($join, 'parent_meta') !== false) {
return $join;
}
// Use prepared statement for security
$join .= $wpdb->prepare(
" LEFT JOIN {$wpdb->postmeta} AS parent_meta ON ({$wpdb->posts}.ID = parent_meta.post_id AND parent_meta.meta_key = %s)",
'_parent_id'
);
return $join;
}
/**
* Custom FIELDS to add parent grouping
*
* @param string $fields FIELDS clause
* @param WP_Query $query Query object
* @return string Modified FIELDS clause
*/
function sv_v2_custom_fields(string $fields, $query): string {
global $wpdb;
// Only apply on frontend product queries
if (!sv_v2_should_modify_query($query)) {
return $fields;
}
// Avoid conflicts with existing fields
if (strpos($fields, 'parent_group') !== false) {
return $fields;
}
$fields .= ", CASE WHEN parent_meta.meta_value IS NOT NULL THEN parent_meta.meta_value ELSE {$wpdb->posts}.ID END AS parent_group";
return $fields;
}
/**
* Custom ORDER BY to group variations with their parent products
*
* @param string $orderby ORDER BY clause
* @param WP_Query $query Query object
* @return string Modified ORDER BY clause
*/
function sv_v2_custom_orderby(string $orderby, $query): string {
global $wpdb;
// Only apply on frontend product queries
if (!sv_v2_should_modify_query($query)) {
return $orderby;
}
// Avoid conflicts with existing ordering
if (strpos($orderby, 'parent_group') !== false) {
return $orderby;
}
// Group by parent product ID, then by product type (parent first, then variations)
$custom_orderby = "
parent_group ASC,
CASE
WHEN parent_meta.meta_value IS NOT NULL THEN 1
ELSE 0
END ASC,
{$wpdb->posts}.menu_order ASC,
{$wpdb->posts}.ID ASC
";
return $custom_orderby;
}
// Hook into WordPress
add_action('woocommerce_product_query', 'sv_v2_modify_product_query', 25);
add_filter('posts_orderby', 'sv_v2_custom_orderby', 10, 2);
add_filter('posts_join', 'sv_v2_custom_join', 10, 2);
add_filter('posts_fields', 'sv_v2_custom_fields', 10, 2); Method 3: Full Plugin Solution
If manually adding code snippets feels cumbersome and you prefer a user-friendly interface, the full plugin version is your best option.
Plugin Features
Admin Settings Panel: Easy configuration through WooCommerce → Settings → Display Variations
Two Toggle Options:
- Display variations as single products: Shows variations alongside the main product
- Hide main product: Hides the main variable product, showing only variations
Enhanced Functionality:
- Settings caching for better performance
- Proper WordPress hooks and filters
- Activation/deactivation notices
- Backward compatibility features
- Security enhancements with prepared statements
Installing the Full Plugin
- Upload Plugin Files
- Activate the Plugin
- Go to Plugins → Installed Plugins
- Find “Display Variations as Single Products”
- Click “Activate”
- Configure Settings
- Navigate to WooCommerce → Settings → Display Variations
- Choose your preferred options:
- Enable “Display variations as single products” to show both main products and variations
- Enable “Hide main product” to show only variations
- Save changes
Key Differences Between Versions
| Feature | Version 1 | Version 2 | Full Plugin |
| Shows main product | Yes | No | Configurable |
| Shows variations | Yes | Yes | Configurable |
| Admin interface | No | No | Yes |
| Easy switching | No | No | Yes |
| Performance optimized | Basic | Basic | Advanced |
Best Practices and Tips
Choose the Right Method:
- Use Version 1 if you want to show both main products and variations
- Use Version 2 if you want a cleaner shop page with only variations
- Use the full plugin if you want flexibility and easy configuration
Test Before Going Live: Always test these modifications on a staging site first to ensure they work correctly with your theme and other plugins.
Theme Compatibility: The code includes checks to prevent conflicts with popular themes like Blocksy, but you should still test with your specific theme.
Performance Considerations: The plugin includes query optimizations and caching to minimize performance impact on your shop pages.
Troubleshooting Common Issues
Variations Not Showing: Make sure your variable products actually have published variations. Unpublished or out-of-stock variations won’t appear.
Conflicts with Other Plugins: If you experience issues, try deactivating other WooCommerce-related plugins temporarily to identify conflicts.
Theme Integration: Some themes may require additional CSS styling to properly display the variations. Check your theme’s documentation for product display customizations.
Final Recommendations
Start with the WpCode snippets to test the functionality on your site. If you like the results and want more control, upgrade to the full plugin for the admin interface and additional features.
The full plugin approach is recommended for most users because it provides flexibility without requiring code modifications when you want to change settings. You can easily switch between showing main products with variations or displaying only variations through the admin panel.
Both approaches will help improve your customer experience by making product variations more visible and accessible on your shop pages.



