GTINs hiding in WooCommerce SKU fields

Plenty of catalogs carry their barcodes in the SKU field, because for years that was the recommended workaround. But feeds, structured data and marketplaces read the GTIN from its own field — and there it is empty. CatalogLift finds each product whose SKU is a valid GTIN while the real field holds nothing.

Sales-readiness Medium severity Fix in CatalogLift

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.

Fair questions, straight answers.

How does a GTIN end up in the SKU field?

Usually on advice. For years WooCommerce had no identifier field, and feed tools openly recommended putting the EAN in the SKU so their mapping could read it from somewhere — a documented anti-pattern that worked until it didn't. Imports from POS systems and wholesale sheets, where the barcode is the only code anyone has, do the same thing quietly.

What's wrong with keeping the barcode as the SKU?

The two codes answer different questions. The SKU is your internal name for the product — yours to shape, yours to change. The GTIN is the world's name for it, and it only counts where systems look for it: the native GTIN, UPC, EAN, or ISBN field, which feeds Google's matching, WooCommerce's structured data and marketplace listings. A barcode parked in the SKU leaves all of that empty.

How does CatalogLift know my SKU is really a GTIN?

Deterministically, and it says so. The SKU is flagged only when it is exactly 12 to 14 digits, its last digit passes the GS1 mod-10 checksum, and the product's own identifier field is empty — and the evidence spells out all three, so you can see why the string was read as a code. A numeric SKU that fails the checksum, or a product whose identifier field is already filled, is left alone.

Does fixing this erase my SKU?

No — nothing changes except on your authority, and the decision is yours: copy the code into the identifier field and keep the SKU as it is, or take the chance to give the product a real internal SKU too. CatalogLift writes exactly what you approve, and the next scan verifies the identifier field carries the code.

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.