How to Display WooCommerce Variations as Individual Products

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
  1. Install WpCode Plugin
    • Go to Plugins → Add New
    • Search for “WpCode”
    • Install and activate the plugin
  2. 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”
  3. Paste the Code Copy the entire contents of this code here below into the code editor.
  4. Set “Auto Insert” to “Run Everywhere”
  5. 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 variations
  • sv_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

  1. 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)”
  2. 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

  1. Upload Plugin Files
  2. Activate the Plugin
    • Go to Plugins → Installed Plugins
    • Find “Display Variations as Single Products”
    • Click “Activate”
  3. 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

FeatureVersion 1Version 2Full Plugin
Shows main productYesNoConfigurable
Shows variationsYesYesConfigurable
Admin interfaceNoNoYes
Easy switchingNoNoYes
Performance optimizedBasicBasicAdvanced

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.

Do you want to thank me and buy me a beer?

Every donation is entirely welcome but NEVER required. Enjoy my work for free but if you would like to thank me and buy me a beer or two then you can use this form here below.

Donation Form (#2)

Here are some of my favorite WordPress tools

Thanks for reading this article! I hope it's been useful as you work on your own websites and e-commerce sites. I wanted to share some tools I use as a WordPress developer, and I think you'll find them helpful too.

Just so you know, these are affiliate links. If you decide to use any of them, I'll earn a commission. This helps me create tutorials and YouTube videos. But honestly, I genuinely use and recommend these tools to my friends and family as well. Your support keeps me creating content that benefits everyone.

Themes: Over the past few years, I've consistently relied on two primary themes for all sorts of projects: the Blocksy theme and the Kadence Theme. If you explore this website and my YouTube channel, you'll come across numerous tutorials that delve into these themes. If you're interested in obtaining a 10% discount for both of these themes, then:

Code Snippets Manager: WPCodeBox allows you to add code snippets to your site. Not only that, but it also provides you with the capability to construct and oversee your WordPress Code Snippets library right in the cloud. You can grab it with the 20% discount here (SAVE 20% Coupon: WPSH20).

Contact forms: There are hundreds of contact forms out there but Fluent Forms is the one I like the most. If you need a 20% discount then use this link (save 20% coupon is WPSH20).

Gutenberg add-ons: If I need a good Gutenberg blocks add-on then Kadence Blocks is the one I have used the most. You’ll get a 10% discount with the coupon SIMPLEHACKS here.

Website migration: While building a website you probably need a good plugin that can help you with the migration, backups, restoration, and staging sites. Well, WpVivid is the one I have used for the last couple of years. If you use this link along with the WPSH20 coupon you’ll get a 20% discount.

Woocommerce extensions: There are a bunch of Woocommerce extensions that I like but the one that stands out is Advanced Dynamic Pricing. Once again, you’ll get a 20% discount if you use this link here (save 20% coupon is WPSH20)

Web Hosting: If you would like to have a really fast and easy-to-use managed cloud hosting, then I recommend Verpex Hosting (see my review here). By the way, this site is hosted in Verpex.)

To see all my most up-to-date recommendations, check out this resource that I made for you!

Janek T.
Janek T.

- I have been passionate about Wordpress since 2011, creating websites and sharing valuable tips on using Wordpress and Woocommerce on my site.
- Be the first to receive notifications about new tutorials by subscribing to my Youtube channel .
- Follow me on Twitter here

Articles: 162