WooCommerce Product CSV Import — Custom Importer with WP-CLI

CodeAtoZ Engineer, CodeAtoZ
12 min read · 2382 words
Share: 𝕏 in
DEV DEEP DIVES · WOOCOMMERCE DEVELOPER TIPS #18

WooCommerce Product CSV Import — Custom Importer with WP-CLI (2026)

Build a WooCommerce product CSV import script with PHP and WP-CLI — column mapping, upsert logic, image import, category creation, and error logging for bulk catalogues.
WP-CLIBatch import
PHP 8+Required
UpsertCreate/update
2026Updated

A custom WooCommerce product CSV import command gives you complete control over how catalogue data maps to WooCommerce products — handling supplier-specific column names, upsert logic (create new or update existing by SKU), category creation, and image download from remote URLs. The built-in WooCommerce product CSV import is powerful but limited to its fixed column format. A custom WP-CLI WooCommerce product CSV import command processes any CSV structure, handles 50,000+ product catalogues in batches without memory issues, and produces a detailed error log so you know exactly which rows failed and why.

WP-CLI is the correct tool for large WooCommerce product CSV import operations because it bypasses web server timeouts, supports configurable memory limits, and produces console output you can pipe to a log file. The import command iterates through the CSV in configurable batch sizes, creates or updates products using WooCommerce’s product API, and maintains a progress counter so you can estimate completion time for large catalogues.

woocommerce product csv import — WP-CLI column mapping upsert logic image import and error logging by CodeAtoZ
WooCommerce product CSV import — full pipeline from CSV parsing through upsert and image download to error logging

Step 1: Build the WooCommerce Product CSV Import WP-CLI Command

Register a WP-CLI command that accepts the CSV file path, batch size, and dry-run flag. The command is the entry point for every WooCommerce product CSV import operation.

PHP — register WP-CLI command for WooCommerce product CSV import
<?php
if ( defined( 'WP_CLI' ) && WP_CLI ) {
    WP_CLI::add_command( 'caz import products', 'CAZ_Product_Import_Command' );
}

class CAZ_Product_Import_Command {

    /**
     * WooCommerce product CSV import command
     *
     * ## OPTIONS
     * <file>
     * : Path to the CSV file
     *
     * [--batch=<size>]
     * : Rows per batch (default: 100)
     *
     * [--dry-run]
     * : Preview without saving
     *
     * ## EXAMPLES
     * wp caz import products catalogue.csv --batch=200
     *
     * @param array $args
     * @param array $assoc_args
     */
    public function __invoke( array $args, array $assoc_args ): void {
        $file      = $args[0] ?? '';
        $batch     = (int) ( $assoc_args['batch']   ?? 100 );
        $dry_run   = isset( $assoc_args['dry-run'] );

        if ( ! file_exists( $file ) ) {
            WP_CLI::error( 'CSV file not found: ' . $file );
        }

        WP_CLI::log( 'Starting WooCommerce product CSV import: ' . $file );
        if ( $dry_run ) WP_CLI::log( '[DRY RUN] No data will be saved.' );

        $importer  = new CAZ_CSV_Importer( $file, $batch, $dry_run );
        $results   = $importer->run();

        WP_CLI::success( sprintf(
            'WooCommerce product CSV import complete. Created: %d | Updated: %d | Failed: %d',
            $results['created'], $results['updated'], $results['failed']
        ));
    }
}

Step 2: WooCommerce Product CSV Import — Column Mapping and Row Processing

The column mapper translates your supplier’s CSV headers to WooCommerce product fields. Define a configurable mapping array so the same importer can handle different CSV formats without code changes.

PHP — column mapping and upsert logic for WooCommerce product CSV import
<?php
class CAZ_CSV_Importer {

    // Column mapping: CSV header => WooCommerce field
    private array $column_map = [
        'product_code'   => 'sku',
        'product_name'   => 'name',
        'description'    => 'description',
        'short_desc'     => 'short_description',
        'trade_price'    => 'regular_price',
        'stock_qty'      => 'stock_quantity',
        'category'       => 'category',
        'image_url'      => 'image_url',
        'weight_kg'      => 'weight',
    ];

    public function __construct(
        private string $file,
        private int    $batch_size,
        private bool   $dry_run
    ) {}

    public function run(): array {
        $handle  = fopen( $this->file, 'r' );
        $headers = fgetcsv( $handle );
        $results = ['created' => 0, 'updated' => 0, 'failed' => 0];
        $row_num = 1;

        while ( ( $row = fgetcsv( $handle ) ) !== false ) {
            $data = array_combine( $headers, $row );
            try {
                $result = $this->process_row( $data );
                $results[ $result ]++;
                if ( $row_num % $this->batch_size === 0 ) {
                    // Clear object cache between batches to prevent memory growth
                    wp_cache_flush();
                    WP_CLI::log( "WooCommerce product CSV import progress: {$row_num} rows processed." );
                }
            } catch ( Exception $e ) {
                $results['failed']++;
                WP_CLI::warning( "Row {$row_num} failed: " . $e->getMessage() );
            }
            $row_num++;
        }

        fclose( $handle );
        return $results;
    }

    private function process_row( array $raw ): string {
        $sku = sanitize_text_field( $raw[ array_search( 'sku', $this->column_map ) ] ?? '' );
        if ( ! $sku ) throw new Exception( 'Missing SKU' );

        // Upsert: find existing product by SKU or create new
        $product_id = wc_get_product_id_by_sku( $sku );
        $product    = $product_id ? wc_get_product( $product_id ) : new WC_Product_Simple();
        $is_new     = ! $product_id;

        // Map CSV columns to WooCommerce product fields
        foreach ( $this->column_map as $csv_col => $wc_field ) {
            $value = sanitize_text_field( $raw[ $csv_col ] ?? '' );
            if ( $value === '' ) continue;
            match ( $wc_field ) {
                'sku'              => $product->set_sku( $value ),
                'name'             => $product->set_name( $value ),
                'description'      => $product->set_description( wp_kses_post( $raw[ $csv_col ] ) ),
                'short_description'=> $product->set_short_description( wp_kses_post( $raw[ $csv_col ] ) ),
                'regular_price'    => $product->set_regular_price( wc_format_decimal( $value ) ),
                'stock_quantity'   => $product->set_stock_quantity( (int) $value ),
                'weight'           => $product->set_weight( $value ),
                default            => null,
            };
        }

        $product->set_status( 'publish' );
        $product->set_catalog_visibility( 'visible' );

        if ( ! $this->dry_run ) {
            $product->save();
            // Handle category and image after save (requires product ID)
            $this->set_category( $product, $raw['category'] ?? '' );
            $this->import_image( $product, $raw['image_url'] ?? '' );
        }

        return $is_new ? 'created' : 'updated';
    }

Step 3: WooCommerce Product CSV Import — Image Download and Category Creation

The image importer downloads remote product images during the WooCommerce product CSV import and attaches them to the product. The category handler creates categories that don’t yet exist, preventing import failures on new catalogues.

PHP — image and category handling in WooCommerce product CSV import
    private function import_image( WC_Product $product, string $url ): void {
        if ( ! $url || ! filter_var( $url, FILTER_VALIDATE_URL ) ) return;
        // Skip if product already has this image (check meta)
        if ( $product->get_meta( '_caz_import_image_url' ) === $url ) return;

        $tmp    = download_url( $url );
        if ( is_wp_error( $tmp ) ) return;

        $file_array = [
            'name'     => basename( parse_url( $url, PHP_URL_PATH ) ),
            'tmp_name' => $tmp,
        ];
        $attach_id = media_handle_sideload( $file_array, $product->get_id() );
        if ( ! is_wp_error( $attach_id ) ) {
            $product->set_image_id( $attach_id );
            $product->update_meta_data( '_caz_import_image_url', $url );
            $product->save();
        }
        @unlink( $tmp );
    }

    private function set_category( WC_Product $product, string $category_name ): void {
        if ( ! $category_name ) return;
        $term = get_term_by( 'name', $category_name, 'product_cat' );
        if ( ! $term ) {
            $result = wp_insert_term( $category_name, 'product_cat' );
            $term_id = is_wp_error( $result ) ? 0 : $result['term_id'];
        } else {
            $term_id = $term->term_id;
        }
        if ( $term_id ) {
            wp_set_object_terms( $product->get_id(), [ $term_id ], 'product_cat' );
        }
    }
}

The WP-CLI command documentation covers command registration and argument syntax for WooCommerce product CSV import scripts. The WooCommerce product data store documentation covers the WC_Product API used for all product CRUD operations. The WordPress media_handle_sideload() documentation covers image import parameters. See our WooCommerce variable product guide for extending this importer to handle variations.

💡 For WooCommerce product CSV import jobs over 10,000 rows, set define('WP_MEMORY_LIMIT', '512M') in wp-config.php and call wp_cache_flush() every 100 rows in the batch loop. Without cache flushing, WooCommerce’s internal object cache grows without bound and the import runs out of memory after a few thousand products.

Frequently Asked Questions — WooCommerce Product CSV Import

Why use WP-CLI for a WooCommerce product CSV import instead of the built-in importer?

The built-in WooCommerce product CSV import is limited to WooCommerce’s fixed column format, runs via the browser (subject to PHP execution timeouts), and can’t handle custom column names without a mapping plugin. A WP-CLI WooCommerce product CSV import command runs without time limits, supports configurable column mapping, processes batches with cache flushing to avoid memory issues, and works for catalogues with hundreds of thousands of products.

How do I update existing products in a WooCommerce product CSV import?

Use wc_get_product_id_by_sku() to check if a product with the row’s SKU already exists. If it does, load the existing product with wc_get_product() and update its fields. If not, create a new WC_Product_Simple. This upsert pattern ensures a WooCommerce product CSV import is idempotent — running the same file twice updates existing products without creating duplicates.

How do I import product images in a WooCommerce product CSV import?

Use WordPress’s download_url() to download the image to a temp file, then media_handle_sideload() to add it to the Media Library and attach it to the product. Store the original image URL in product meta so subsequent WooCommerce product CSV import runs can skip re-downloading unchanged images. Always validate the URL with filter_var() before attempting to download and clean up the temp file after sideloading.

How do I prevent running out of memory during a WooCommerce product CSV import?

Call wp_cache_flush() every batch (e.g. every 100 rows) inside the import loop. WordPress’s and WooCommerce’s internal caches accumulate product objects during a WooCommerce product CSV import and cause memory usage to grow linearly without flushing. Also call wc_delete_product_transients() periodically. Set the PHP memory limit to at least 512MB for large WooCommerce product CSV import operations via WP-CLI: wp –memory=512M caz import products.

How do I create categories automatically during a WooCommerce product CSV import?

Use get_term_by(‘name’, $category_name, ‘product_cat’) to check if the category exists. If it doesn’t exist, call wp_insert_term() to create it. Use the returned term ID with wp_set_object_terms() to assign it to the product after saving. This pattern makes your WooCommerce product CSV import self-contained — you don’t need to pre-create categories before running the import.

Can I use the WooCommerce product CSV import command for variable products?

Yes — extend the process_row() method to detect rows where a parent SKU column is present. Create a WC_Product_Variable for the parent and WC_Product_Variation for each variant row. Match variations to their parent by the parent SKU. The column mapping approach in this WooCommerce product CSV import guide scales to variable products by adding parent_sku, attribute_name, and attribute_value columns to the CSV format and handling them in the category/image equivalent steps.

Need a Custom WooCommerce Product Importer Built?

CodeAtoZ builds bespoke WooCommerce product CSV import scripts — custom column mapping, variable products, image import, and scheduled sync from supplier feeds.

CodeAtoZ
Lead Engineer & Founder, CodeAtoZ

10+ years building Magento 2 and WooCommerce platforms for high-volume e-commerce businesses. Writes about performance engineering, plugin architecture, and scalable commerce systems.

Join the Discussion

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