Duplicate SKUs in WooCommerce

A SKU is the one code that fulfilment, accounting and feeds trust to mean exactly one thing. When two WooCommerce records share it, every system downstream can pick the wrong one. CatalogLift finds each duplicate group across products and variations and lists the colliding records as evidence.

Sales-readiness High severity Fix in CatalogLift

Why duplicate SKUs cost you orders

Inside your store the SKU looks like a label. Outside it, the SKU is the key. Warehouse software, accounting exports, marketplace feeds and stock synchronization all look up products by SKU, and a lookup that returns two rows behaves unpredictably.

  • Orders pick the wrong product. A pick list, a packing slip or a fulfilment integration that resolves a SKU may land on the other product, and the customer receives the wrong item.
  • Stock counts drift. Inventory tools that sync by SKU write one product's quantity onto the other, so one record oversells while the other sits on phantom stock.
  • Feeds and imports collide. Google Merchant Center and marketplace feeds treat the SKU as the item identifier; duplicates get rejected or silently overwrite each other, and the next CSV import cannot tell which row it should update.
  • Reporting merges two histories. Sales and returns for two different products get summed under one code, which makes the numbers you plan on quietly wrong.

How to find duplicate SKUs manually

WooCommerce stores the SKU in postmeta under _sku, for both products and variations. With WP-CLI, this query lists every exact-match SKU that appears more than once among published records:

wp db query "SELECT m.meta_value AS sku,
       COUNT(*) AS uses,
       GROUP_CONCAT(p.ID ORDER BY p.ID) AS post_ids
FROM wp_postmeta m
JOIN wp_posts p ON p.ID = m.post_id
WHERE m.meta_key = '_sku'
  AND m.meta_value <> ''
  AND p.post_type IN ('product', 'product_variation')
  AND p.post_status = 'publish'
GROUP BY m.meta_value
HAVING uses > 1
ORDER BY uses DESC, sku;"

Adjust the table prefix if yours isn't wp_. The post_ids column tells you which products or variations to open in wp-admin.

This works, with the same three gaps as most one-off queries. It only catches byte-for-byte matches, so ABC-123 and abc-123, or a SKU pasted with a trailing space or a typographic dash, slip past it while your warehouse system treats them as the same code. It does not check whether a variation's parent is itself published, so it can report collisions shoppers never see. And it answers once: the next import or API sync can reintroduce the same collision the following week with nobody watching.

How CatalogLift finds it

Every scan reads the SKU of each published product and each published variation of a published parent, and compares them after normalization: Unicode composition, invisible zero-width characters removed, dashes of every kind reduced to a plain hyphen, spaces around hyphens dropped, repeated whitespace collapsed, and case ignored. Records without a SKU are skipped, since an empty value is a different problem. Any normalized SKU shared by more than one record becomes a duplicate group, and every member of the group gets its own finding at high severity, carrying the raw SKU, the normalized form, how many records share it, and a sample of the colliding products and variations as evidence.

There is no AI in this check, because no model can know which of two products deserves the code. Instead you fix it in CatalogLift: with the whole group in front of you, you type the correct SKU for the record that should change, approve it, and CatalogLift writes it to WooCommerce. Nothing changes in the store before that decision, and the next scan verifies the group is gone. Because CatalogLift keeps watching, a future import that recreates the collision surfaces as a fresh finding instead of a silent regression.

Fair questions, straight answers.

Doesn't WooCommerce already block duplicate SKUs?

Only in the product editor. CSV imports, REST API writes, migration plugins and direct database edits can all save a SKU that another product or variation already uses, and WooCommerce keeps both without a warning.

What counts as a duplicate SKU?

CatalogLift compares normalized SKUs, so ABC-123, abc-123, ABC – 123 with a typographic dash and the same code with stray spaces or invisible characters all fall into one group. Products and variations are compared together, so a parent product sharing its SKU with someone else's variation is caught too.

Does CatalogLift choose the new SKU for me?

No. There is no AI in this check. The finding shows every record in the duplicate group; you type the correct SKU inside CatalogLift, approve the change, and CatalogLift writes it to WooCommerce. The next scan verifies the collision is gone.

Are draft or private products included?

No. The scan reads published products and the published variations of published parents, and it ignores records without a SKU. Two products with an empty SKU are not a duplicate; they are a missing SKU, which is a separate check.

See it on your own catalog.

Create a free account, connect your WooCommerce store and this check runs on your real products, along with the rest.

No card required. The free plan never writes to WooCommerce.