Guide · E-commerce data

Shopify product feed audit: find the data behind Merchant Center disapprovals

When Shopify products are disapproved or have limited visibility in Google Merchant Center, start with Merchant Center itself. Products → Needs attention lists every product-level issue and the attribute it's about. Fix that first. It's the only place that tells you Google's actual reason.

What Merchant Center doesn't show well is the pattern across your catalogue: how many variants lack a barcode, which products to fix first. That you can audit from the storefront.

The usual data problems, from Google's own requirements

Auditing a catalogue from the storefront

Most Shopify stores serve their catalogue at /products.json (limit up to 250, plus page). It isn't a documented API, so treat it as best-effort. Two catches:

  1. Barcodes and image alt text aren't in /products.json. They're in each product's own /products/{handle}.json, so sample and fetch those.
  2. The feed stops at 25,000 products. Bigger stores need the public sitemap and collection feeds too. Compare your count with the store's.
import random, time, requests

STORE = "https://your-store.com"          # your storefront domain
UA = {"User-Agent": "feed-audit-script/1.0"}

def gtin_ok(code):
    d = "".join(ch for ch in str(code or "") if ch.isdigit())
    if len(d) not in (8, 12, 13, 14):
        return False
    body, check = [int(x) for x in d[:-1]], int(d[-1])
    total = sum(n * (3 if i % 2 == 0 else 1) for i, n in enumerate(reversed(body)))
    return (10 - total % 10) % 10 == check

products, page = [], 1
while True:
    r = requests.get(f"{STORE}/products.json", params={"limit": 250, "page": page}, headers=UA, timeout=20)
    r.raise_for_status()
    batch = r.json()["products"]
    if not batch:
        break
    products += batch
    page += 1
    time.sleep(1)

no_type = sum(1 for p in products if not (p["product_type"] or "").strip())
no_vendor = sum(1 for p in products if not (p["vendor"] or "").strip())
no_image = sum(1 for p in products if not p["images"])

variants = bad_gtin = 0
for p in random.sample(products, min(20, len(products))):   # barcodes live on each product's own JSON
    full = requests.get(f"{STORE}/products/{p['handle']}.json", headers=UA, timeout=20).json()["product"]
    for v in full["variants"]:
        variants += 1
        bad_gtin += not gtin_ok(v.get("barcode"))
    time.sleep(1)

print(f"{len(products)} products | no product type: {no_type} | no vendor: {no_vendor} | no image: {no_image}")
print(f"sampled variants without a valid GTIN: {bad_gtin}/{variants}")

The check-digit test won't catch a well-formed GTIN that belongs to a different product, and it doesn't check the restricted prefixes. Treat it as a first pass.

We built this

Disclosure: Your Evenings Ltd built and sells this. Shopify Products Scraper & AI Shopping Readiness Audit on the Apify Store runs this kind of audit over any list of Shopify stores:

To be clear about what it isn't: it reads public storefront data only and changes nothing in Shopify. It can't see your Merchant Center account or Google's reason for a disapproval, and a good score doesn't guarantee approval.

FAQ

Where do I see why a Shopify product was disapproved? In Google Merchant Center, under Products → Needs attention.

Which Shopify field does Google use as the GTIN? The variant's Barcode field. For products without one, Shopify suggests adding brand and MPN in the Google & YouTube channel.

Should I make up a GTIN to clear the error? No. Google says not to guess or invent GTINs. Products without a GTIN don't need one.

Product names and logos belong to their owners. Your Evenings Ltd is not affiliated with or endorsed by them.