Create a Custom WooCommerce Product Loop with a Shortcode

Build a custom WooCommerce product loop with a shortcode that displays up to six recent featured products. The example returns your own list markup with each product’s image, title, price, and WooCommerce add-to-cart control.

This tutorial keeps the WP_Query approach for developers customizing classic PHP output. For new product-query integrations, WooCommerce recommends wc_get_products() or WC_Product_Query to reduce dependence on product storage details.

What Is the Product Loop?

The product loop is the collection of products displayed on a shop, category, or archive page. A custom product loop lets you control that markup directly — useful for a featured-products shortcode, a custom homepage section, or anywhere the default WooCommerce archive template doesn’t fit.

The shortcode below shows a fixed-size featured selection. It is not a paginated shop archive, a related-products algorithm, or a replacement for WooCommerce’s block-based product collections.

1. Install the Product Loop Shortcode

Back up your site and test on staging. Add the code once to a small site-specific plugin or your child theme’s functions.php. If the file already has an opening PHP tag, omit the example’s surrounding PHP tags. WooCommerce must be active, and you need published featured products that are visible in the catalog and not marked out of stock.

<?php
add_shortcode( 'custom_loop', 'wpwebguru_custom_product_loop' );
function wpwebguru_custom_product_loop() {
    if ( ! function_exists( 'WC' ) || ! WC()->query ) {
        return '';
    }

    global $product;
    $previous_product = $product;
    ob_start();

    $meta_query = WC()->query->get_meta_query();

    $args = array(
        'post_type'      => 'product',
        'post_status'    => 'publish',
        'posts_per_page' => 6,
        'no_found_rows'  => true,
        'orderby'        => 'date',
        'order'          => 'DESC',
        'meta_query'     => $meta_query,
        'tax_query'      => array(
            'relation' => 'AND',
            array(
                'taxonomy' => 'product_visibility',
                'field'    => 'slug',
                'terms'    => array( 'featured' ),
                'operator' => 'IN',
            ),
            array(
                'taxonomy' => 'product_visibility',
                'field'    => 'slug',
                'terms'    => array( 'outofstock', 'exclude-from-catalog' ),
                'operator' => 'NOT IN',
            ),
        ),
    );

    $loop = new WP_Query( $args );
    ?>
    <ul class="custom-product-loop">
        <?php
        while ( $loop->have_posts() ) :
            $loop->the_post();
            $product = wc_get_product( get_the_ID() );
            if ( ! $product || ! $product->is_visible() ) {
                continue;
            }
            ?>
            <li>
                <?php
                if ( has_post_thumbnail( get_the_ID() ) ) {
                    echo get_the_post_thumbnail( get_the_ID(), 'shop_catalog' );
                } else {
                    echo '<img src="' . esc_url( wc_placeholder_img_src() ) . '" alt="' . esc_attr__( 'Placeholder', 'wpwebguru' ) . '" width="65" height="115">';
                }
                ?>
                <h3><?php the_title(); ?></h3>
                <?php
                echo $product->get_price_html();
                woocommerce_template_loop_add_to_cart();
                ?>
            </li>
        <?php endwhile; ?>
    </ul>
    <?php
    wp_reset_postdata();
    $product = $previous_product;

    return ob_get_clean();
}
?>

2. Add the Shortcode to a Page

Place [custom_loop] in a Shortcode block or a shortcode-enabled content area. The name takes no attributes in this example, so adding limit or category attributes will not change its behavior. It outputs each product’s featured image, title, price, and add-to-cart button inside your own <ul>/<li> structure, ready to style with your own CSS.

How the Product Query Works

  • Featured products: The query uses the featured term in product_visibility. It does not depend on the legacy _featured meta field.
  • Visibility and stock: The query excludes exclude-from-catalog and outofstock terms. This example deliberately hides out-of-stock products regardless of the store’s display setting, and checks the loaded product’s visibility before rendering.
  • Fixed limit: At most six matching posts are requested, newest first. A product rejected by an additional visibility filter may leave fewer than six cards.
  • Product context: Each product is loaded before its price and button are rendered. The prior global product is restored after the custom loop.
  • Query cleanup: wp_reset_postdata() restores the main post context. no_found_rows avoids a total-count query because this shortcode does not paginate.
  • Button behavior: WooCommerce chooses the appropriate control for the product type; variable products may show a selection link rather than a direct add-to-cart button.

3. Adjust the Product Selection

A few common variations worth knowing:

  • For a recent-products selection, remove only the featured taxonomy clause. Keep the catalog-visibility and stock exclusions, and retain any meta-query filters your integration needs.
  • Filter by category by adding a tax_query entry for product_cat — see our guide on getting WooCommerce product categories for the taxonomy details.
  • For specialized sorting or filtering, prefer the documented WooCommerce product-query APIs. Avoid assuming internal meta keys will remain the supported storage interface.

Performance Note

Keep product limits bounded. Before adding custom caching, measure the query and check the page/object cache already in place. Cached product output can become stale when prices, stock, visibility, currency, or customer-specific pricing changes; do not share personalized markup across shoppers. Our SQL query optimization guide covers query and caching fundamentals.

Test and Troubleshoot the Shortcode

  • Mark a published, catalog-visible product as featured, then verify its image, title, price, and product-type-specific button.
  • Confirm that a hidden product, draft, and out-of-stock product are excluded.
  • If the list is empty, check featured status and visibility before changing the query.
  • Test the page with no featured products and with fewer than six; the example may return an empty list.
  • Verify that content and product details rendered after the shortcode still use their original context.

For a paginated custom listing, see our custom loop with pagination tutorial. Pagination requires query arguments and navigation together; simply increasing the limit does not add it.

Wrapping Up

This shortcode gives you a bounded featured-product selection with custom HTML, visibility checks, and restored loop context. Keep the existing shortcode name when updating pages that already use it, and test theme styling and product-type behavior before using the example on a store.

Leave a Reply

Your email address will not be published. Required fields are marked *

2 replies on “Create a Custom WooCommerce Product Loop with a Shortcode”

The link has been Copied to clipboard!