WooCommerce product images — the featured image and the product gallery — are stored as post thumbnail and post meta respectively, and can be managed fully from PHP. Programmatic gallery management is essential for product import scripts, custom product editors, and headless WooCommerce setups.
Problem: WooCommerce product images are uploaded manually through the admin — bulk importing products via CSV or the REST API does not handle image attachment from remote URLs, leaving products with no gallery images after import.
Solution: Use media_sideload_image() to download a remote image and attach it to a post. For bulk imports, process images in an async background job using wp_schedule_single_event() to avoid timeout limits. Store the remote URL as product meta during import and process image attachment separately after the product is created.
The examples below set the featured image, add gallery images from URLs, reorder the gallery, and filter the product gallery output in the block-based product template.
basename( parse_url( $image_url, PHP_URL_PATH ) ),
'tmp_name' => $tmp,
];
// Sideload into the media library and attach to the product
$attach_id = media_handle_sideload( $file_array, $product_id );
if ( is_wp_error( $attach_id ) ) {
@unlink( $tmp );
return $attach_id;
}
// Set as featured (main product) image
set_post_thumbnail( $product_id, $attach_id );
return $attach_id;
}
// ── ADD GALLERY IMAGES FROM URL ARRAY ──
function myplugin_set_product_gallery( int $product_id, array $image_urls ): void {
$gallery_ids = [];
foreach ( $image_urls as $url ) {
$attach_id = myplugin_set_product_image_from_url( $product_id, $url );
if ( ! is_wp_error( $attach_id ) ) {
$gallery_ids[] = $attach_id;
}
}
// Save gallery IDs — WooCommerce stores them in _product_image_gallery meta
$product = wc_get_product( $product_id );
if ( $product ) {
$product->set_gallery_image_ids( $gallery_ids );
$product->save();
}
}
// ── READ GALLERY IMAGES ──
$product = wc_get_product( 42 );
$gallery_ids = $product->get_gallery_image_ids(); // array of attachment IDs
foreach ( $gallery_ids as $id ) {
$src = wp_get_attachment_image_src( $id, 'woocommerce_product_gallery_thumbnail' );
// $src[0] = URL, $src[1] = width, $src[2] = height
}
Reorder gallery images and filter gallery output:
get_gallery_image_ids();
$valid = array_filter( $ordered_ids, fn( $id ) => in_array( $id, $current, true ) );
$product->set_gallery_image_ids( array_values( $valid ) );
$product->save();
}
// ── FILTER GALLERY OUTPUT ──
// Inject a video thumbnail as the first gallery item
add_filter( 'woocommerce_single_product_image_gallery_classes', function( array $classes ): array {
$classes[] = 'has-video';
return $classes;
} );
// Add a custom image size for the product gallery
add_image_size( 'product-gallery-zoom', 1200, 1200, false );
// Register the custom size as a WooCommerce gallery image size
add_filter( 'woocommerce_get_image_size_gallery_thumbnail', function( array $size ): array {
return [ 'width' => 150, 'height' => 150, 'crop' => 1 ];
} );
add_filter( 'woocommerce_get_image_size_gallery_full', function( array $size ): array {
return [ 'width' => 1200, 'height' => 1200, 'crop' => 0 ];
} );
// ── REST API: get gallery images ──
// GET /wp-json/wc/v3/products/{id}
// Response includes:
// "images": [
// {"id": 45, "src": "...", "position": 0, ...}, <- position 0 is featured
// {"id": 46, "src": "...", "position": 1, ...}, <- gallery images start at 1
// ]
// To update gallery via REST, send PUT with the "images" array in the desired order.
NOTE: WooCommerce regenerates image sizes via wp wc tool run regenerate_thumbnails --user=admin. After adding the product-gallery-zoom image size, run this command to generate it for existing products. Set woocommerce_gallery_image_size to your custom size name to use it in the gallery lightbox.