Why a GTIN in the SKU field doesn't count
Before WooCommerce 9.1 shipped the native GTIN, UPC, EAN, or ISBN field, the SKU was the only code field a product had, and feed tools documented the workaround openly: put the EAN there and map it out later. The catalogs built on that advice still work — for the tool that gave it. Everything that reads the standard field reads a blank.
- Google sees a product without a GTIN. Merchant Center takes the GTIN from the identifier attribute, not the SKU. With the real field empty the offer runs at limited performance — weaker matching, fewer placements — while the code sits one field over.
- Structured data goes out without the code. WooCommerce builds the schema.org gtin from its own field. A barcode in the SKU never reaches the product's JSON-LD, so rich results and free listings can't match the product either.
- Marketplaces ask for what the field doesn't hold. Marketplaces require a barcode and comparison engines match by brand, manufacturer code and barcode. Listing tools read the identifier field, find it empty, and the workaround becomes manual re-entry per listing.
- The SKU stops being yours. An internal code you can rename and restructure is now a global constant you dare not touch, and two jobs share one field — which is how the next variant ends up with a made-up “barcode” as its SKU.
How to find GTINs in SKU fields manually
Both codes live in postmeta: the SKU under _sku, the
identifier under _global_unique_id. This WP-CLI query
lists published records whose SKU is 12 to 14 digits while the
identifier field is missing or empty:
wp db query "SELECT p.ID, p.post_title, s.meta_value AS sku
FROM wp_posts p
JOIN wp_postmeta s
ON s.post_id = p.ID AND s.meta_key = '_sku'
LEFT JOIN wp_postmeta g
ON g.post_id = p.ID AND g.meta_key = '_global_unique_id'
WHERE p.post_type IN ('product', 'product_variation')
AND p.post_status = 'publish'
AND s.meta_value REGEXP '^[0-9]{12,14}$'
AND (g.meta_value IS NULL OR g.meta_value = '')
ORDER BY p.ID;"
Adjust the table prefix if yours isn't wp_. The regex
catches the right shape but not the checksum, so a numeric internal
SKU that merely looks like a barcode lands on the list too.
That last gap is the real work. Twelve digits prove nothing — only the GS1 checksum separates a barcode from a numbering scheme, and SQL won't compute it. Each row needs a checksum run and a judgment call, and the next import from the same POS system refills the list the following week.
How CatalogLift finds it
Every scan reads both fields of each product in the synced catalog. A record is flagged only when three things are true at once: the SKU is exactly 12 to 14 digits, its final digit passes the GS1 mod-10 checksum, and the record's own identifier field is empty. Each finding, at medium severity, spells out that reasoning as evidence — the digit count, the passed checksum, the empty field — so you can see why the string was read as a code, and disagree if you know better. A numeric SKU that fails the checksum is left alone, and so is any product whose identifier field is already filled.
There is no AI on this path — the check is three deterministic facts, and the fix is yours to shape. Copy the code into the identifier field and keep the SKU, or give the product a proper internal SKU in the same pass; either way you approve the exact change inside CatalogLift before it writes anything to WooCommerce, and the next scan verifies the identifier field carries the code. Because CatalogLift keeps watching, the next import that parks barcodes in the SKU column surfaces as fresh findings instead of a quiet habit, and the first scan costs nothing and needs no card.