Properly Retrieve WooCommerce Products in Your Template

wc_get_products() and WC_Product_Query are the correct way to retrieve products in WooCommerce. They are functionally equivalent to get_posts and WP_Query in WordPress core, and they accept an array of arguments to define your query criteria.

Avoid using WP_Query or direct database queries for WooCommerce products. WooCommerce’s database structure changes between versions (most recently with HPOS – High-Performance Order Storage), and direct queries may break after updates.

wc_get_products and WC_Product_Query Examples

Here are some common examples. The code comments explain what each query does:

// Get downloadable products created in the year 2016.
$products = wc_get_products( array(
    'downloadable' => true,
    'date_created' => '2016-01-01...2016-12-31',
) );
// Get 10 most recent product IDs in date descending order.
$query = new WC_Product_Query( array(
    'limit' => 10,
    'orderby' => 'date',
    'order' => 'DESC',
    'return' => 'ids',
) );
$products = $query->get_products();
// Get products containing a specific SKU.
// Does partial matching, so this will get products with SKUs "PRDCT-1", "PRDCT-2", etc.
$query = new WC_Product_Query();
$query->set( 'sku', 'PRDCT' );
$products = $query->get_products();

WC_Product_Query Methods

These are the methods available on a WC_Product_Query instance:

  • get_query_vars() – Get an array of all the current query variables set on the query object.
  • get( string $query_var, mixed $default = ” ) – Get the value of a query variable or the default if the query variable is not set.
  • set( string $query_var, mixed $value ) – Set a query variable to a value.
  • get_products() – Get all products matching the current query variables.

Parameters

General

status – Accepts a string or an array of strings: one or more of the following options: ‘draft’, ‘pending’, ‘private’, ‘publish,’ or custom status.

// Get draft products.
$args = array(
    'status' => 'draft',
);
$products = wc_get_products( $args );

type – Accepts a string or an array of strings: one or more of the following options: ‘external’, ‘grouped’, ‘simple’, ‘variable,’ or custom type.

// Get external products.
$args = array(
    'type' => 'external',
);
$products = wc_get_products( $args );

include – Accepts an array of integers: include only product IDs that are in this array.

// Get external products limited to ones with specific IDs.
$args = array(
    'type' => 'external',
    'include' => array( 134, 200, 210, 340 ),
);
$products = wc_get_products( $args );

exclude – Accepts an array of integers: exclude product IDs that are in this array.

// Get products that aren't the current product.
$args = array(
    'exclude' => array( $product->get_id() ),
);
$products = wc_get_products( $args );

parent – Accepts an integer: the ID of the parent product.

// Get products with a specific parent.
$args = array(
    'parent' => 20,
);
$products = wc_get_products( $args );

parent_exclude – Accepts an array of integers: exclude products with parent IDs that are in this array.

// Get products that don't have parent IDs of 20 or 21.
$args = array(
    'parent_exclude' => array( 20, 21 ),
);
$products = wc_get_products( $args );

limit – Accepts an integer: maximum number of results to retrieve or -1 for unlimited.

// Get the latest 3 products.
$args = array(
    'limit' => 3,
);
$products = wc_get_products( $args );

page – Accepts an integer: the page of results that the function should work on. Irrelevant if the offset parameter is in use.

// First 3 products.
$args = array(
    'limit' => 3,
    'page'  => 1,
);
$page_1_products = wc_get_products( $args );

// Second 3 products.
$args = array(
    'limit' => 3,
    'page'  => 2,
);
$page_2_products = wc_get_products( $args );

paginate – Responsible for pagination – accepts a boolean variable, True or False. Default is False.

// Get products with extra info about the results.
$args = array(
    'paginate' => true,
);
$results = wc_get_products( $args );
echo $results->total . ' products foundn';
echo 'Page 1 of ' . $results->max_num_pages . 'n';
echo 'First product id is: ' . $results->products[0]->get_id() . 'n';

offset – Accepts an integer: the offset for the product results.

// Get the second to fifth most recent products.
$args = array(
    'limit' => 4,
    'offset' => 1
);
$products = wc_get_products( $args );

order – Accepts a string: the parameters ‘DESC’ or ‘ASC’. Use it in conjunction with ‘orderby’. Default is ‘DESC’.

// Get most recently modified products.
$args = array(
    'orderby' => 'modified',
    'order' => 'DESC',
);
$products = wc_get_products( $args );

orderby – Accepts a string: the parameters are ‘none’, ‘ID’, ‘name’, ‘type’, ‘rand’, ‘date’, ‘modified’. Default is ‘date’.

// Get some random products.
$args = array(
    'orderby' => 'rand',
);
$products = wc_get_products( $args );

return – Accepts a string: the parameters are ‘ids’ or ‘objects’. Default is ‘objects’.

// Get product ids.
$args = array(
    'return' => 'ids',
);
$products = wc_get_products( $args );

Product

sku – Accepts a string: checks if the string exists in the product’s SKU.

// Get products with "PRDCT" in their SKU (e.g., PRDCT-1 and PRDCT-2).
$args = array(
    'sku' => 'PRDCT',
);
$products = wc_get_products( $args );

tag – Accepts an array: limits the results to products with specific tags based on their slug.

// Get products with the "Excellent" or "Modern" tags.
$args = array(
    'tag' => array( 'excellent', 'modern' ),
);
$products = wc_get_products( $args );

category – Accepts an array: limits the results to products with specific categories based on their slug.

// Get shirts.
$args = array(
    'category' => array( 'shirts' ),
);
$products = wc_get_products( $args );

weight, length, width, height – Accepts float values: the dimensions the products should have.

// Get products 5.5 units wide and 10 units long.
$args = array(
    'width' => 5.5,
    'length' => 10,
);
$products = wc_get_products( $args );

price, regular_price, sale_price – Accepts float values: the price at which the results should be.

// Get products that currently cost 9.99.
$args = array(
    'price' => 9.99,
);
$products = wc_get_products( $args );

total_sales – Accepts an integer: retrieves products with a specific number of sales.

// Get products that have never been purchased.
$args = array(
    'total_sales' => 0,
);
$products = wc_get_products( $args );

virtual, downloadable, featured, sold_individually, manage_stock, reviews_allowed – Accepts a Boolean variable: limits results to products with specific settings or features.

// Get downloadable products that don't allow reviews.
$args = array(
    'downloadable' => true,
    'reviews_allowed' => false,
);
$products = wc_get_products( $args );

backorders – Accepts a string: options are ‘yes’, ‘no’, or ‘notify’.

// Get products that allow backorders.
$args = array(
    'backorders' => 'yes',
);
$products = wc_get_products( $args );

visibility – Accepts a string: options are ‘visible’, ‘catalog’, ‘search’, or ‘hidden’.

// Get products that show in the catalog.
$args = array(
    'visibility' => 'catalog',
);
$products = wc_get_products( $args );

stock_quantity – Accepts an integer: the quantity of the product in stock.

// Get products that only have one left in stock.
$args = array(
    'stock_quantity' => 1,
);
$products = wc_get_products( $args );

stock_status – Accepts a string: the status of the products in stock – ‘outofstock’ or ‘instock’.

// Get out of stock products.
$args = array(
    'stock_status' => 'outofstock',
);
$products = wc_get_products( $args );

tax_status – Accepts a string: options are ‘taxable’, ‘shipping’, or ‘none’.

// Get taxable products.
$args = array(
    'tax_status' => 'taxable',
);
$products = wc_get_products( $args );

tax_class – Accepts a string.

// Get products in the "Reduced Rate" tax class.
$args = array(
    'tax_class' => 'reduced-rate',
);
$products = wc_get_products( $args );

shipping_class – Accepts a string or an array of strings.

// Get products in the "Bulky" shipping class.
$args = array(
    'shipping_class' => 'bulky',
);
$products = wc_get_products( $args );

download_limit, download_expiry – Accepts an integer: the restriction on the number of downloads, where -1 means unlimited.

// Get products with unlimited downloads.
$args = array(
    'download_limit' => -1,
);
$products = wc_get_products( $args );

average_rating – Accepts a float: the average rating of the product.

// Get products with all 5-star ratings.
$args = array(
    'average_rating' => 5.0,
);
$products = wc_get_products( $args );

review_count – Accepts an integer: the number of reviews for the product.

// Get products with 1 review.
$args = array(
    'review_count' => 1,
);
$products = wc_get_products( $args );

Date

date_created, date_modified, date_on_sale_from, date_on_sale_to – Accepts a string: retrieves products based on the date with the format YYYY-MM-DD or TIMESTAMP.

// Get downloadable products created in the year 2016.
$products = wc_get_products( array(
    'downloadable' => true,
    'date_created' => '2016-01-01...2016-12-31',
) );

Add Custom Query Parameters

Use the woocommerce_product_data_store_cpt_get_products_query filter to add support for custom parameters:

/**
 * Handle a custom 'customvar' query var to get products with the 'customvar' meta.
 * @param array $query - Args for WP_Query.
 * @param array $query_vars - Query vars from WC_Product_Query.
 * @return array modified $query
 */
function handle_custom_query_var( $query, $query_vars ) {
    if ( ! empty( $query_vars['customvar'] ) ) {
        $query['meta_query'][] = array(
            'key' => 'customvar',
            'value' => esc_attr( $query_vars['customvar'] ),
        );
    }

    return $query;
}
add_filter( 'woocommerce_product_data_store_cpt_get_products_query', 'handle_custom_query_var', 10, 2 );

Then use it like any built-in parameter:

$products = wc_get_products( array( 'customvar' => 'somevalue' ) );

Since WooCommerce 8.2, HPOS (High-Performance Order Storage) is enabled by default for new installations. While HPOS primarily affects orders (not products), it is a reminder that WooCommerce’s data layer evolves over time. Using wc_get_products() instead of direct queries ensures your code stays compatible.

FAQs

Common questions about retrieving WooCommerce products:

What is the difference between wc_get_products() and WC_Product_Query?
They are functionally equivalent. wc_get_products() is a wrapper function that creates a WC_Product_Query internally. Use wc_get_products() for simple one-off queries, and WC_Product_Query when you need to modify the query object before executing it (for example, setting parameters dynamically with the set() method).
Can I use WP_Query to get WooCommerce products?
While WP_Query with post_type => 'product' technically works, it is not recommended. WooCommerce may change its underlying data storage between versions (as it did with HPOS for orders). Using wc_get_products() ensures your queries remain compatible with future WooCommerce updates.
How do I get all products in a specific category?
Pass the category slug in the category parameter: wc_get_products( array( 'category' => array( 'shirts' ), 'limit' => -1 ) ). Set limit to -1 to retrieve all matching products without a cap.
How do I query products by custom fields (meta)?
Use the woocommerce_product_data_store_cpt_get_products_query filter to register a custom query variable, then pass it to wc_get_products(). Inside the filter callback, add a meta_query array targeting your custom meta key and value.
Does wc_get_products() return product objects or IDs?
By default it returns an array of WC_Product objects. To get only product IDs (which is faster for large queries), set 'return' => 'ids' in the arguments array.

Summary

wc_get_products() and WC_Product_Query are the recommended way to retrieve products in WooCommerce. They support a wide range of parameters for filtering by status, type, category, tag, price, stock, dates, and more. For custom filtering needs, the woocommerce_product_data_store_cpt_get_products_query filter lets you add your own query variables while keeping your code future-proof.

Discussion
[  0 Comments  ]

Leave a Comment

To add code, use the buttons below. For instance, click the PHP button to insert PHP code within the shortcode. If you notice any typos, please let us know!