Why a restricted prefix is a rejection waiting to happen
GS1 doesn't hand out every number. The ranges beginning
02, 04 and 2xx are reserved
for restricted distribution — codes a store or a POS system mints for
its own shelves, weighed goods and bundles — and 05,
98 and 99 belong to coupons. Inside the
store that printed them they work fine. Submitted to the outside
world as a product's GTIN, they are a claim that cannot be true.
- Google disapproves the offer. Merchant Center checks the prefix along with length and checksum, and a restricted range fails that check outright: the offer is disapproved, the same penalty a mangled code gets — not the softer limited performance of a missing one.
- Marketplaces won't take the listing. Any channel that checks a barcode against the GS1 database before it accepts an offer treats a restricted range as invalid, and blocks creating or editing the listing entirely.
- Comparison engines match it to nothing. They identify products by brand, manufacturer code and barcode. An in-store code exists in no public registry, so the product never joins its comparison page.
- Every ordinary validator waves it through. The checksum is correct, the length is correct, the digits are digits. A spreadsheet check or a quick script sees a healthy code, which is why these survive audits that catch every typo.
How to find restricted prefixes manually
The code is stored in postmeta under
_global_unique_id. Restricted ranges depend on the
code's length — 2 opens the restricted block in a
13-digit EAN, while 02, 04 and the coupon
range 05 lead 12-digit UPCs — so the query checks
prefix and length together:
wp db query "SELECT p.ID, p.post_title, m.meta_value AS gtin
FROM wp_posts p
JOIN wp_postmeta m
ON m.post_id = p.ID AND m.meta_key = '_global_unique_id'
WHERE p.post_type IN ('product', 'product_variation')
AND p.post_status = 'publish'
AND m.meta_value REGEXP '^[0-9]+$'
AND (
(LENGTH(m.meta_value) = 13 AND LEFT(m.meta_value, 1) = '2')
OR (LENGTH(m.meta_value) = 13 AND LEFT(m.meta_value, 2) IN ('98', '99'))
OR (LENGTH(m.meta_value) = 12 AND LEFT(m.meta_value, 2) IN ('02', '04', '05'))
)
ORDER BY p.ID;"
Adjust the table prefix if yours isn't wp_. Codes with
the wrong length or characters are a different problem — the invalid
GTIN check — and are deliberately left out here.
The query finds the codes; it cannot find the replacements. Each hit means tracing where the code came from — a POS export, a labelling tool, a supplier sheet — and then obtaining the product's real GTIN, which is legwork no SQL shortens. And the next export from the same system will plant the same codes again.
How CatalogLift finds it
Every scan validates each product's identifier field deterministically, and a code that passes length and checksum is then checked against the GS1 restricted and coupon ranges for its length. A code on one of those ranges becomes a finding at high severity, and the evidence names the range and what it is reserved for — an in-store restricted-distribution code, a coupon code — so you can tell at a glance which system most likely minted it.
There is no AI on this path, and the fix is deliberately not an edit: changing a digit until the prefix looks public would forge a claim on a code you don't own. The real fix is the real code — from the supplier or the packaging for resold products, from your own GS1 license for goods you brand yourself — typed into CatalogLift and decided by you. Only then does CatalogLift write it to WooCommerce, and the next scan verifies the new code is valid and public. Because CatalogLift keeps watching, the next POS export that reintroduces in-store codes surfaces immediately, and the first scan costs nothing and needs no card.