GTINs hidden in WooCommerce plugin fields

Plenty of stores do have their barcodes — inside an EAN plugin's own meta field, added years before WooCommerce had a native one. The code renders on the product page and everything looks done, while the field that Google's structured data, the feed plugins and the CSV export actually read stays empty. CatalogLift finds each product whose code lives only in a plugin field.

Sales-readiness Medium severity Fix in CatalogLift

Why a code in a plugin field isn't seen

Before WooCommerce 9.1 shipped the native GTIN, UPC, EAN, or ISBN field, plugins filled the gap, each with its own meta key: WPFactory's EAN plugin (default _alg_ean, but the key is configurable), Yoast WooCommerce SEO's wpseo_global_identifier_values array, closed plugins that left _wpm_gtin_code or hwp_product_gtin behind, feed tools writing _wc_gla_gtin, fb_gtin or _ts_gtin, SEO suites with seopress_barcode and _rank_math_gtin_code, and legacy installs still carrying _flexible_ean from before version 2.0, which never migrated automatically. The codes are real — they just live where only one plugin looks.

  • The JSON-LD goes out without the code. WooCommerce builds the product's schema.org gtin from the native field alone. A code in a plugin's meta key never reaches the structured data, so Google matches the product from name and brand — the weak way — while the barcode sits one key over.
  • Feed plugins read a blank. A comparison-engine or marketplace feed takes the barcode from WooCommerce's own field, not from whichever plugin happens to hold it. The store has the code; the feed says it doesn't.
  • CSV exports ship without it. WooCommerce's built-in export writes the native field's column. Migrations, backups and spreadsheets handed to an agency all carry an empty GTIN column over a catalog full of codes.
  • The gap hides behind a working product page. The plugin renders the code in the storefront, so the one place a human looks is the one place that shows it — and every system that reads the standard field quietly sees nothing.

How to find plugin-held GTINs manually

Both values live in postmeta, so the check is a self-join: one alias for the plugin's key, one for the native _global_unique_id. This WP-CLI query lists products where WPFactory's default key holds a code while the native field is missing or empty:

wp db query "SELECT p.ID, p.post_title, e.meta_value AS plugin_ean
FROM wp_posts p
JOIN wp_postmeta e
  ON e.post_id = p.ID AND e.meta_key = '_alg_ean'
LEFT JOIN wp_postmeta g
  ON g.post_id = p.ID AND g.meta_key = '_global_unique_id'
WHERE p.post_type = 'product'
  AND p.post_status = 'publish'
  AND e.meta_value != ''
  AND (g.meta_value IS NULL OR g.meta_value = '')
ORDER BY p.ID;"

Adjust the table prefix if yours isn't wp_, and swap _alg_ean for whichever key your stack uses — _wc_gla_gtin, _wpm_gtin_code, seopress_barcode and the rest each need their own run.

And that is the catch: one query covers one key. WPFactory's key is whatever the alg_wc_ean_meta_key option says it is, so the query above misses any store that changed the default; Yoast stores six identifier types together in one serialized array that SQL string-matching reads badly. Covering a real store means a query per plugin, per key, per configuration — and rerunning the lot after every import.

How CatalogLift finds it

Every scan reads the plugin fields the way each plugin does. The WPFactory key comes from the alg_wc_ean_meta_key option, never from a hard-coded constant — a store that moved its key years ago must not look like a store with no codes. Yoast's wpseo_global_identifier_values array is read positionally in its fixed order — gtin8, gtin12, gtin13, gtin14, isbn — so the value arrives named by the slot it sat in, and the mpn slot is never mistaken for a code. A product whose native field is empty while a plugin field holds a code becomes a finding at medium severity, and the evidence names the source and the exact meta key the value came from, so you can see where the code lives before you move it.

CatalogLift then validates the code it found and proposes one change: copy it into the native _global_unique_id field. The code is always one your store already holds — this check rewrites an existing value to where the standard readers look; it never creates a new one, and there is no AI on the path. You review the before/after diff and approve it inside CatalogLift. Nothing is written before that decision; on it, CatalogLift writes to WooCommerce and the next scan verifies the native field carries the code. Because CatalogLift keeps watching, the next import that lands codes in a plugin key surfaces as fresh findings instead of a quiet gap, and the first scan costs nothing and needs no card.

Fair questions, straight answers.

My EAN plugin shows the code on the product page — isn't that enough?

It is enough for that plugin, and for nothing else. WooCommerce builds the product's JSON-LD gtin from its own native field, feed plugins read the same field, and so does the built-in CSV export. Each of those sees a blank until the code also lives in _global_unique_id — the field WooCommerce 9.1 added for exactly this.

Which plugin fields does CatalogLift read?

The ones stores actually use: WPFactory's EAN plugin (whose meta key is configurable — CatalogLift reads the alg_wc_ean_meta_key option rather than assuming the default _alg_ean), Yoast WooCommerce SEO's wpseo_global_identifier_values array, the closed _wpm_gtin_code and hwp_product_gtin fields, feed keys like _wc_gla_gtin, fb_gtin, _ts_gtin, seopress_barcode and _rank_math_gtin_code, and the legacy _flexible_ean key that pre-2.0 installs never migrated. Every finding names the source and the exact meta key the value came from.

Does CatalogLift invent a code if the plugin field is empty too?

Never. This check only moves a code that already exists in your store — it reads the plugin's value, validates it, and proposes copying it into the native field. If no field anywhere holds a code, that is a different finding (a missing GTIN), and the answer there is the same: a GTIN is assigned by GS1, looked up, never generated.

Will the fix break my EAN plugin?

No — the proposal fills the native field; it does not delete the plugin's. The plugin keeps working exactly as before, and everything that reads the standard field starts working too. You see the before/after diff, approve it, CatalogLift writes it to WooCommerce, and the next scan verifies the native 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.