WooCommerce product galleries — the thumbnail strip below the main product image that lets shoppers view additional photos — are one of the highest-impact elements on a product page. By default, WooCommerce renders the gallery using its own template and Flexslider/Photoswipe JavaScript. But sometimes you need to output the gallery in a custom template, a shortcode, a widget, or an AJAX response where the default template output is not available. WooCommerce stores gallery image IDs in the _product_image_gallery post-meta key as a comma-separated list of attachment IDs. You retrieve the main image with get_post_thumbnail_id() and the gallery images with $product->get_gallery_image_ids(). From those IDs you can generate whatever HTML structure you need using standard WordPress attachment functions: wp_get_attachment_image(), wp_get_attachment_image_src(), and wp_get_attachment_image_url(). For a lightbox effect, you can leverage the built-in WooCommerce Photoswipe integration or add your own data attributes for a custom lightbox library. This pattern is especially useful when combined with the product price display guide and shortcode patterns to build custom product showcase components. The example below outputs a fully functional gallery with the main image, thumbnail strip, and Photoswipe-compatible data attributes.
Problem: You need to display a WooCommerce product’s main image and gallery thumbnails in a custom template or shortcode outside of the standard single-product template.
Solution: Add the following code to your functions.php or template file:
/**
* Output a WooCommerce product gallery for any product by ID.
*
* @param int $product_id
* @return string HTML output
*/
function helloadmin_product_gallery( int $product_id ) {
$product = wc_get_product( $product_id );
if ( ! $product ) {
return '';
}
$main_id = $product->get_image_id();
$gallery_ids = $product->get_gallery_image_ids();
$all_ids = $main_id ? array_merge( [ $main_id ], $gallery_ids ) : $gallery_ids;
if ( empty( $all_ids ) ) {
return wc_placeholder_img( 'woocommerce_single' );
}
ob_start();
echo '<div class="helloadmin-product-gallery">';
// Main image (first in array)
$main_src = wp_get_attachment_image_src( $all_ids[0], 'woocommerce_single' );
if ( $main_src ) {
printf(
'<a href="%s" class="gallery-main" data-photoswipe-src="%s">%s</a>',
esc_url( $main_src[0] ),
esc_url( wp_get_attachment_url( $all_ids[0] ) ),
wp_get_attachment_image( $all_ids[0], 'woocommerce_single', false, [
'class' => 'gallery-main-img',
'alt' => esc_attr( $product->get_name() ),
] )
);
}
// Thumbnail strip
if ( count( $all_ids ) > 1 ) {
echo '<div class="gallery-thumbs">';
foreach ( $all_ids as $attachment_id ) {
$thumb_src = wp_get_attachment_image_src( $attachment_id, 'thumbnail' );
if ( $thumb_src ) {
printf(
'<a href="%s" class="gallery-thumb" data-full="%s">%s</a>',
esc_url( $thumb_src[0] ),
esc_url( wp_get_attachment_url( $attachment_id ) ),
wp_get_attachment_image( $attachment_id, 'thumbnail', false, [
'class' => 'gallery-thumb-img',
'alt' => esc_attr( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ),
] )
);
}
}
echo '</div>';
}
echo '</div>';
return ob_get_clean();
}
// Use as a shortcode: [helloadmin_gallery id="42"]
add_shortcode( 'helloadmin_gallery', function( $atts ) {
$atts = shortcode_atts( [ 'id' => 0 ], $atts );
return helloadmin_product_gallery( absint( $atts['id'] ) );
} );
NOTE: wc_placeholder_img() returns WooCommerce’s default grey placeholder image when no product image is set — always include this fallback to avoid empty image elements on products without photos. If you need a responsive gallery with swipe support on mobile, consider adding data- attributes compatible with Swiper.js or Splide (both lightweight alternatives to the default Flexslider) rather than building swipe handling from scratch.