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.