WooCommerce: Remove Product Image Backgrounds Automatically (WP-CLI Command + API)
WooCommerce stores rarely have studio photography for every SKU; they have supplier photos on beige tables and phone shots on the shop floor. The fix does not need a plugin subscription: a 60-line mu-plugin that adds a WP-CLI command, sends each product image to a background removal API with a white fill, sideloads the result into the media library and sets it as the product image. Run it once for the catalogue, then on new products.
In this guide
The mu-plugin
Save as wp-content/mu-plugins/bgclear-woo.php. It uses wp_remote_post with a JSON body (image_file_b64), so there is no multipart code and no cURL dependency, and it keeps the originals — the cutout is added as a new attachment.
<?php
/**
* Plugin Name: bgclear for WooCommerce (WP-CLI)
* Description: wp bgclear products — white-background product images via the bgclear API.
*/
if (!defined('WP_CLI') || !WP_CLI) { return; }
class BgClear_Woo_Command {
/**
* Process product images. [--dry-run] uses free preview size. [--limit=<n>]
*/
public function products($args, $assoc) {
$key = defined('BGCLEAR_API_KEY') ? BGCLEAR_API_KEY : getenv('BGCLEAR_API_KEY');
$size = isset($assoc['dry-run']) ? 'preview' : 'full';
$limit = (int) ($assoc['limit'] ?? 100);
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
$products = wc_get_products(['limit' => $limit, 'status' => 'publish']);
foreach ($products as $product) {
if ($product->get_meta('_bgclear_done')) { continue; }
$thumb_id = $product->get_image_id();
$path = $thumb_id ? get_attached_file($thumb_id) : null;
if (!$path || !file_exists($path)) { continue; }
$res = wp_remote_post('https://www.bgclear.ai/api/v1/remove', [
'timeout' => 90,
'headers' => [
'Authorization' => 'Bearer ' . $key,
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Idempotency-Key' => 'woo-' . $thumb_id . '-' . md5_file($path),
],
'body' => wp_json_encode([
'image_file_b64' => base64_encode(file_get_contents($path)),
'size' => $size, 'format' => 'jpg', 'bg_color' => 'ffffff',
]),
]);
$code = wp_remote_retrieve_response_code($res);
$data = json_decode(wp_remote_retrieve_body($res), true);
if ($code !== 200) {
WP_CLI::warning(sprintf('#%d %s: %s', $product->get_id(), $code, $data['error']['message'] ?? 'error'));
if ($code === 402) { WP_CLI::error('Out of credits — https://www.bgclear.ai/api-pricing/'); }
continue;
}
// Sideload the hosted result (valid 24 h) into the media library.
$new_id = media_handle_sideload(
['name' => $product->get_slug() . '-white.jpg', 'tmp_name' => download_url($data['url'])],
$product->get_id(), $product->get_name() . ' on white background'
);
if (is_wp_error($new_id)) { WP_CLI::warning($new_id->get_error_message()); continue; }
$product->set_image_id($new_id);
$product->update_meta_data('_bgclear_done', current_time('mysql'));
$product->save();
WP_CLI::log(sprintf('#%d %s → attachment %d (credits left %s)',
$product->get_id(), $product->get_name(), $new_id, $data['credits_remaining'] ?? '?'));
}
WP_CLI::success('Done.');
}
}
WP_CLI::add_command('bgclear', 'BgClear_Woo_Command');Define the key in wp-config.php (define('BGCLEAR_API_KEY', '…');) or the environment, never in the plugin file.
Running it
# free dry run on 20 products — preview size, no credits used
wp bgclear products --dry-run --limit=20
# the real thing
wp bgclear products --limit=500The _bgclear_done meta flag makes the command resumable and prevents double processing; the idempotency header makes a retry of an interrupted request free for 24 hours. Gallery images: loop $product->get_gallery_image_ids() the same way and call set_gallery_image_ids() with the new ids.
On upload instead of in bulk
To process new product images as they are added, hook woocommerce_admin_process_product_object (fires on product save in wp-admin) and call the same request for the product's image id when _bgclear_done is empty. Do it via wp_schedule_single_event or Action Scheduler rather than inline — a 5-second API round trip inside the save request is where editors get impatient.
Why JPG on white, and what it costs
Marketplace feeds (Google Shopping, Meta, Amazon) and most themes expect a solid white main image, and JPG on white is a fraction of the size of a transparent PNG, which helps Core Web Vitals on category pages. If your theme uses coloured cards, request format: 'png' without bg_color for a transparent cutout instead.
Cost: one credit per full-resolution image — $9 for 100, $39 for 500, $129 for 2,000; credits never expire, and the dry run is free (pricing). Stores that used a remove.bg plugin should note its API ends on 1 December 2026; the request above is field-compatible, see the migration page.
Frequently asked questions
Is there a plugin I can install instead?
This mu-plugin is the plugin: one file, no settings screen, runs from WP-CLI. Managed hosts that block WP-CLI can run the same code from a custom admin action or a cron event.
Will this overwrite my original product images?
No. The cutout is added as a new attachment and set as the product image; the original stays in the media library.
Does it work with large supplier images?
Synchronous full-size requests are capped at 4 megapixels. Resize on upload (WordPress's big_image_size_threshold does this by default at 2560 px) or route larger files through POST /api/v1/jobs.
What does it cost for a 300-product store?
300 credits — the $39 pack covers 500 images at $0.078 each, and the remaining credits never expire.