WP All Import at Scale: Fixing XPath Conflicts, Dollar Signs, and Field Mapping That Breaks Silently

WP All Import at Scale: Fixing XPath Conflicts, Dollar Signs, and Field Mapping That Breaks Silently

WP All Import is the right tool for bulk WooCommerce product imports. It handles CSV, XML, and repeating field structures that the native WooCommerce importer can’t touch. But when you’re importing 500+ products with custom ACF fields, role-based pricing, and supplier CSVs that weren’t built with your import in mind, it will break in ways that are not obvious from the error messages.

These are the specific problems I hit on the Diversified Air catalog build — five customer tiers, 500+ SKUs, seven-plus ACF fields per product — and how to fix them.

The @ Symbol XPath Conflict

In XPath, @ is reserved syntax for attribute selectors. If any of your CSV column headers contain an @ character (which happens more than you’d expect with supplier CSVs that include email fields or internal notation like MSRP@Dealer), WP All Import will fail to parse that column in ways that are nearly impossible to debug from the UI alone.

The symptom: the field appears available in the column mapping interface, but the mapped data never makes it into the imported post. No error. Just silently empty fields.

The fix is pre-processing the CSV before it touches WP All Import. Rename the column headers in your spreadsheet application or via a quick script:

#!/usr/bin/env python3
# Clean problematic characters from CSV headers before import
import csv, sys

input_file  = 'supplier_raw.csv'
output_file = 'supplier_clean.csv'

with open(input_file) as infile, open(output_file, 'w', newline='') as outfile:
    reader = csv.reader(infile)
    writer = csv.writer(outfile)
    headers = next(reader)
    # Strip @, $, and other XPath-conflicting characters from headers
    clean_headers = [h.replace('@', '_at_').replace('$', '_price_') for h in headers]
    writer.writerow(clean_headers)
    writer.writerows(reader)

Run this before every import cycle and keep the original supplier file untouched. The cleaned file is what WP All Import sees.

Dollar Signs in Number Fields

WP All Import’s number field type strips formatting characters from values — but it doesn’t handle the dollar sign as a prefix on values that also contain a dollar sign in the column header. The combination of a $ in the header name and a $ prefix in the cell value creates a double-processing issue that results in the field importing as zero or empty.

This one also breaks silently — no import error, just wrong data. The same pre-processing script above handles the header side. For the cell values, add a second pass:

# Also strip $ from price cell values during pre-processing
for row in reader:
    cleaned_row = []
    for i, cell in enumerate(row):
        # Strip $ from cells in price columns (identified by index)
        if i in price_column_indices:
            cleaned_row.append(cell.replace('$', '').replace(',', '').strip())
        else:
            cleaned_row.append(cell)
    writer.writerow(cleaned_row)

Merging Multiple Supplier CSVs

If your product catalog comes from multiple suppliers, you’ll often need to merge CSVs before import. The catch: column names won’t match exactly across suppliers, and some products will appear in multiple files with conflicting data.

The approach that works:

  • Standardize column headers first — create a master header map and normalize each supplier file to it before merging. Don’t trust that “SKU” and “sku” and “Item Number” mean the same thing just because they contain the same data.
  • Deduplicate on SKU — use the SKU as your deduplication key. When the same SKU appears in multiple files, decide your precedence rule (primary supplier wins, or most recent modification date wins) and stick to it.
  • Test the merge on 20 rows before running 500 — import a small sample, verify the field mapping in WP admin, then run the full import. A mapping error on 500 products creates a cleanup problem that takes longer to fix than the import itself.

ACF Field Mapping for Repeater Fields

WP All Import’s ACF add-on handles repeater fields, but the XPath syntax for repeater sub-fields is not well documented. The pattern that works for a flat CSV (one row per product, with repeater data in separate columns) uses a static row count of 1:

// WP All Import XPath for ACF Repeater sub-fields from flat CSV
// Assumes one repeater row per product (pricing matrix per product)

// Number of rows: always 1 for flat CSV
1

// Sub-field value mapping (for each sub-field in the repeater)
{pricing_tier_dealer[1]}
{pricing_tier_contractor[1]}
{pricing_tier_distributor[1]}

Don’t use the ACF field name as the XPath key. Use the CSV column header (after cleaning). WP All Import maps CSV columns by their header names, not by your ACF field keys. If your CSV column is dealer_price and your ACF field is pricing_tier_dealer, the XPath reference is {dealer_price[1]}.

Add the Pricing Hooks After the Import Is Verified

If you’re also implementing custom pricing hooks (like the woocommerce_product_get_price filter approach covered in the role-based pricing post), add those after you’ve verified the import data is correct. Some import sequences trigger pricing hooks during the import process itself, which can cause your custom pricing logic to overwrite the imported values before they’re saved.

The safe order: import → verify data in wp-admin → then activate pricing hooks. If your hooks are already live during import, temporarily deactivate that code, run the import, verify, then reactivate.

Caveats

  • WP All Import’s “update existing records” mode uses your chosen unique identifier to match. If you change the identifier between import runs (from SKU to product ID, for example), you’ll create duplicates instead of updates
  • Image imports from URL are slow and prone to timeout on large catalogs — batch image imports separately from product data imports if possible
  • WP All Import stores its import history and template in the database. On large repeating imports, periodically clean old import records to avoid table bloat