# feedlab knowledge base > feedlab is a Shopify feed engine: it syncs a store's catalogue, maps every field through a transformation rule engine, and publishes schema-perfect product feeds for Google Shopping, Meta, Microsoft, Pinterest, OpenAI and any other channel on a schedule. This document contains the full text of the feedlab knowledge base, a free reference for building and fixing product feeds on Shopify. Generated 17 September 2026. Content is free to quote and cite with attribution to feedlab (www.feedlab.io). --- # Sales channels ## Google Shopping Google Shopping is the surface where Google displays product listings from a merchant's catalogue, fed by a product feed uploaded to Google Merchant Center. Google's product data specification is the most detailed of the major channels, and the one whose attribute names the other channels tend to borrow. - **Canonical URL:** https://www.feedlab.io/knowledge/channels/google-shopping - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Feed specification | Property | Value | | --- | --- | | Destination | Google Merchant Center | | Refresh | A scheduled fetch can run daily, weekly or monthly, and Merchant Center re-fetches the feed URL on that schedule. Product data must not be older than 30 days or items expire, so a feed that stops regenerating will silently empty out. | #### Accepted formats - `Text (CSV/TSV)` - `XML (RSS 2.0)` - `XML (Atom 1.0)` - `Content API for Shopping` ### What catches Shopify merchants out #### Shopify variants are separate products to Google Each Shopify variant becomes its own row with its own id, and all variants of one product share an item_group_id. Submitting one row per product instead of per variant loses every size and colour a shopper could filter on. #### Google crawls your landing page and compares it to the feed Price and availability in the feed are checked against the structured data and visible content on the product page. A feed that is correct but stale gets disapproved for mismatching, which is why regeneration cadence matters as much as mapping. #### Shopify descriptions carry HTML that Google does not want The body_html field is rich text. Submitted as-is it brings markup into the description attribute, which Google rejects or truncates awkwardly. The description needs the HTML stripped and entities decoded first. #### Barcodes live on the variant, not the product Shopify stores the barcode on each variant, and it is frequently blank because it is optional in the admin. An empty barcode is the single most common reason a Shopify catalogue fails GTIN validation. ### Frequently asked questions #### What attributes are required for a Google Shopping feed? Every item needs id, title, description, link, image_link, availability and price. Beyond that, brand and a product identifier (gtin or mpn) are required for most products, condition is required when an item is not new, and item_group_id is required for variants. #### How often does Google Merchant Center fetch a scheduled feed? You choose daily, weekly or monthly when you set up the scheduled fetch, and Google pulls the URL on that cadence. Because product data expires after 30 days, a daily fetch is the safe default for any catalogue where price or stock moves. #### Can I submit a Shopify feed without GTINs? Yes, but only for products that genuinely have no manufacturer-assigned GTIN, such as custom or handmade goods. In that case you supply brand and mpn instead, or set identifier_exists to no. Omitting GTINs on branded retail products that do have them causes disapprovals. ### Primary sources - [Google: Product data specification](https://support.google.com/merchants/answer/7052112) - [Google: Upload a feed to Merchant Center](https://support.google.com/merchants/answer/188477) --- ## Meta Meta's product catalogue is the data source behind Facebook and Instagram Shops, Advantage+ catalogue ads and dynamic retargeting, populated by a product feed uploaded to Commerce Manager. Its attribute names largely mirror Google's, but its accepted values and its handling of variants differ enough to break a feed copied straight across. - **Canonical URL:** https://www.feedlab.io/knowledge/channels/meta - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Feed specification | Property | Value | | --- | --- | | Destination | Meta Commerce Manager catalogue | | Refresh | Scheduled feeds can be fetched hourly, daily or weekly, which is a finer granularity than Google offers. Meta also supports pushing individual updates through the Catalog Batch API for stock changes that cannot wait for the next scheduled pull. | #### Accepted formats - `CSV` - `TSV` - `XML (RSS 2.0)` - `XML (Atom 1.0)` - `Google Sheets` ### What catches Shopify merchants out #### condition is required, not optional Google only requires condition when an item is not new. Meta requires it on every item. A feed built to Google's rules and pointed at Meta will fail on every row until condition is mapped. #### Availability values have historically differed Meta's documented values use underscores (in_stock, out_of_stock), matching Google, and its older specification used spaces. Feeds built years ago against the space-separated form still circulate in templates, so check what your source actually emits rather than assuming. #### Every variant needs its own row and a shared item_group_id As with Google, a Shopify product with three sizes is three catalogue items. Meta groups them for the variant picker in Shops through item_group_id, and without it each size appears as an unrelated product. #### Image requirements are stricter for Shops Catalogue ads tolerate most images, but Shops surfaces enforce a minimum resolution and prefer square or near-square crops. Shopify's default product image is whatever the merchant uploaded, so the same feed can pass for ads and fail for Shops. ### Frequently asked questions #### What is the difference between a Meta catalogue feed and a Google Shopping feed? The attribute names overlap heavily, but Meta requires condition on every item where Google only requires it for non-new products, and Meta supports hourly scheduled fetches where Google's minimum is daily. Category values also differ: Google uses its own product taxonomy, Meta uses its own category list. #### How often can Meta fetch a scheduled catalogue feed? Meta supports hourly, daily and weekly scheduled fetches. For catalogues where stock moves quickly, hourly fetches combined with the Catalog Batch API for urgent changes keep the catalogue closer to the store than a daily pull can. #### Do I need a separate feed for Facebook and Instagram? No. Facebook and Instagram both read from the same Meta catalogue, so one feed into Commerce Manager serves Shops, catalogue ads and dynamic retargeting across both surfaces. ### Primary sources - [Meta: Product catalogue data feed reference](https://www.facebook.com/business/help/120325381656392) - [Meta: Supported feed formats](https://www.facebook.com/business/help/125074381480892) --- ## Microsoft Advertising Microsoft Advertising runs product ads across Bing, Yahoo and the Microsoft Audience Network from a catalogue uploaded to Microsoft Merchant Center. Its feed specification is closely modelled on Google's, which makes it the cheapest additional channel to launch once a Google Shopping feed already exists. - **Canonical URL:** https://www.feedlab.io/knowledge/channels/microsoft-bing - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Feed specification | Property | Value | | --- | --- | | Destination | Microsoft Merchant Center | | Refresh | Scheduled fetches run on a daily cadence. Microsoft also supports importing a Google Merchant Center account directly, which copies the catalogue across rather than reading your feed URL, and that copy can drift from the source. | #### Accepted formats - `Text (TSV)` - `XML (RSS 2.0)` - `XML (Atom 1.0)` ### What catches Shopify merchants out #### The Google import is a copy, not a mirror Importing from Google Merchant Center is the fastest way to launch, but it creates a separate catalogue that syncs on its own schedule. Publishing a feed URL to both channels from one mapping keeps them genuinely identical. #### It uses the Google product taxonomy Microsoft accepts Google's product category taxonomy values, so the google_product_category mapping carries over unchanged. This is the main reason a Google feed can be repointed with so little work. #### Identifier rules follow Google closely but not exactly Microsoft expects the same gtin, mpn and brand combination logic as Google. Where the two differ is in enforcement: an item that Google merely warns about can be rejected outright, so a feed that looks healthy in Merchant Center is still worth checking here. ### Frequently asked questions #### Can I use my Google Shopping feed for Microsoft Advertising? Largely yes. Microsoft's specification mirrors Google's attribute names and reuses the Google product taxonomy, so the same mapping usually works. Publishing the same feed URL to both is more reliable than Microsoft's Google Merchant Center import, which creates a copy that can drift. #### Does Microsoft Advertising require GTINs? Microsoft applies broadly the same identifier rules as Google, expecting a gtin or an mpn together with a brand for products that have them. Enforcement can be stricter, so items that only trigger a warning on Google may be rejected here. ### Primary sources - [Microsoft: Feed file guidelines](https://help.ads.microsoft.com/#apex/ads/en/56895/1) - [Microsoft Merchant Center product feed attributes](https://help.ads.microsoft.com/#apex/ads/en/51084/1) --- ## OpenAI OpenAI ingests a merchant product feed so ChatGPT can surface products in shopping answers and complete purchases through Instant Checkout. It is the one major channel that does not fetch a feed URL you host: you push full catalogue snapshots to an OpenAI SFTP endpoint, and the most recently processed snapshot is retained for up to 14 days. - **Canonical URL:** https://www.feedlab.io/knowledge/channels/openai - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Feed specification | Property | Value | | --- | --- | | Destination | ChatGPT product discovery and Instant Checkout | | Refresh | Feeds are pushed to OpenAI over SFTP rather than fetched from a URL, and the guidance is to publish a full catalogue snapshot on a predictable cadence of at least daily. OpenAI retains its most recently processed record for up to 14 days, so a catalogue that stops being pushed ages out rather than simply going stale. | #### Accepted formats - `Parquet (zstd, preferred)` - `JSONL (.jsonl.gz)` - `CSV (.csv.gz)` - `TSV (.tsv.gz)` ### What catches Shopify merchants out #### You push the feed, it is not fetched Every other channel here takes a feed URL and re-fetches it on a schedule. OpenAI expects full snapshots pushed to an SFTP endpoint it issues you. A feed built around "publish a URL and let the channel pull it" needs a delivery step added rather than just a mapping. #### Price is one string, currency included The price attribute is a decimal amount, a space, then an uppercase ISO 4217 code, so "79.99 USD" rather than an amount and a currency in separate columns. Shopify holds the two apart, so this is a formatting rule, not a field you can map straight across. #### seller_name is required and is not a product field Alongside the expected item_id, title, description, url, brand, image_url, availability and price, the spec requires seller_name. Nothing on a Shopify product carries it, so it has to come from a static value set once for the store. #### Variants use a different shape to Google Rather than Google's item_group_id, variants are grouped with group_id, flagged with listing_has_variations, and described by a variant_dict mapping option names to values. A feed already mapped for Google needs this rebuilt rather than renamed. ### Frequently asked questions #### How do I submit a product feed to OpenAI? You push it. OpenAI issues an SFTP endpoint and you upload full catalogue snapshots to it, rather than hosting a feed URL for the channel to fetch. The recommended cadence is a full snapshot at least once a day. #### What happens if my OpenAI feed stops updating? OpenAI retains its most recently processed record for up to 14 days. If snapshots stop arriving, products age out of that window and stop being eligible for ChatGPT shopping surfaces, so a broken delivery empties the catalogue rather than freezing it. #### Which file formats does the OpenAI product feed accept? Parquet compressed with zstd is preferred. JSONL is the reference format for the schema, and gzipped JSONL, CSV and TSV are also accepted. The Google-compatible variant lets you reuse tab or comma delimited files you already generate, with some attribute differences. ### Primary sources - [OpenAI: Product feed required fields](https://developers.openai.com/commerce/specs/file-upload/products) - [OpenAI: File upload overview](https://developers.openai.com/commerce/specs/file-upload/overview) --- ## Pinterest Pinterest turns an uploaded product feed into Product Pins, which are shoppable pins that carry live price and availability from the catalogue. Its feed specification borrows Google's attribute names, and its distinguishing requirement is that every item needs a genuinely usable image, because the image is the entire unit of discovery on Pinterest. - **Canonical URL:** https://www.feedlab.io/knowledge/channels/pinterest - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Feed specification | Property | Value | | --- | --- | | Destination | Pinterest catalogues | | Refresh | Pinterest fetches a scheduled feed once every 24 hours, at a time you choose. There is no sub-daily scheduled fetch, so price and stock accuracy depends on the feed being regenerated before Pinterest pulls it. | #### Accepted formats - `CSV` - `TSV` - `XML (RSS 2.0)` - `XML (Atom 1.0)` ### What catches Shopify merchants out #### The image is the product On Google a poor image costs you a little click-through. On Pinterest the image is the listing, and vertical or near-square crops materially outperform the landscape images many Shopify themes produce. It is worth mapping a dedicated image where one exists. #### One feed per locale, not one feed with locales Pinterest expects a separate feed per country and language combination rather than a single feed carrying every market. A Shopify store selling into several markets needs one generated feed per market. #### Product Pins go stale quietly Because the fetch is daily and there is no push API for routine updates, a sold-out product can stay pinned as available for most of a day. Regenerating the feed shortly before the scheduled fetch narrows that window as far as the channel allows. ### Frequently asked questions #### How often does Pinterest update a product feed? Pinterest fetches a scheduled feed once every 24 hours at a time you choose. There is no more frequent scheduled option, so the practical way to keep Product Pins accurate is to regenerate your feed shortly before Pinterest's fetch window. #### Do I need a separate Pinterest feed for each country? Yes. Pinterest expects one feed per country and language combination rather than a single multi-market feed, so a Shopify store selling into several markets needs a generated feed per market. ### Primary sources - [Pinterest: Data source ingestion and formatting](https://help.pinterest.com/en/business/article/data-source-ingestion) - [Pinterest: Catalogues overview](https://help.pinterest.com/en/business/article/before-you-get-started-with-catalogues) --- # Feed attributes ## availability The availability attribute tells a channel whether an item can be bought right now, using a fixed vocabulary rather than a stock count. On Shopify it cannot be read from inventory quantity alone, because a variant that does not track inventory or that allows overselling is purchasable at zero quantity. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/availability - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `availability` | | Plain name | Stock availability | | Data type | Enumerated value | | Format | One of a fixed set of values, lowercase with underscores: in_stock, out_of_stock, preorder, backorder. Channels differ on which of the four they accept, and older specifications used space-separated forms such as "in stock" that still circulate in templates. | #### Example values - `in_stock` - `out_of_stock` - `preorder` - `backorder` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required | `availability` | Crawled from the landing page and compared against the feed. | | Meta | Required | `availability` | Also accepts available_for_order and discontinued. | | Microsoft Advertising | Required | `availability` | | | Pinterest | Required | `availability` | Only refreshed once every 24 hours. | | OpenAI | Required | `availability` | Accepts in_stock, out_of_stock, pre_order, backorder or unknown. The Google-compatible variant spells the third one preorder. | ### Frequently asked questions #### How do I work out availability from Shopify inventory? Quantity alone is not enough. A variant is purchasable if it does not track inventory at all, or if it tracks inventory and has quantity above zero, or if it allows overselling when out of stock. Only a variant that tracks inventory, has no stock and denies overselling is genuinely out_of_stock. #### What are the valid availability values? The core vocabulary is in_stock, out_of_stock, preorder and backorder, written lowercase with underscores. Channels differ on which they accept, and Meta additionally recognises available_for_order and discontinued. #### Why does Google say my availability does not match my site? Usually the feed is stale. If a product sells out after the last feed generation, the feed still says in_stock while the crawled page says sold out. Regenerating the feed on a schedule that reflects how fast stock moves is the fix. ### Primary sources - [Google: availability attribute specification](https://support.google.com/merchants/answer/6324448) --- ## brand The brand attribute is the name of the brand a product belongs to, and it is what makes a manufacturer part number meaningful and a listing eligible for brand-based matching. On Shopify it maps most often from the vendor field, which is unreliable because merchants commonly use vendor to record a supplier or distributor rather than the consumer-facing brand. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/brand - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `brand` | | Plain name | Brand name | | Data type | Text | | Limit | 70 characters | | Format | The consumer-facing brand name as it appears on the product, not a supplier, distributor or internal category. For own-brand goods, use your own store's brand name. Do not use "Generic", "N/A", "None" or a placeholder: channels treat those as missing rather than as a declaration. | #### Example values - `Northwind` - `Acme Tools` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required in some cases | `brand` | Required for all products except movies, books and musical recordings. | | Meta | Recommended | `brand` | | | Microsoft Advertising | Required in some cases | `brand` | Expected alongside gtin or mpn. | | Pinterest | Optional | `brand` | | | OpenAI | Required | `brand` | Required, and sits alongside a required seller_name that no Shopify product field supplies, so that one comes from a static store-level value. | ### Frequently asked questions #### Can I map brand from the Shopify vendor field? Often, but check it first. Shopify's vendor field is free text with no validation, and many stores use it for the supplier, the distributor or an internal grouping rather than the brand a shopper would recognise. Where it is unreliable, a metafield or a lookup table mapping vendor values to real brand names is more dependable. #### What do I put in brand for own-brand products? Your own store or product-line brand name. Every product a shopper could identify by brand should carry one, and for own-brand goods that brand is you. #### Which products do not need a brand? Google exempts movies, books and musical recordings, which are identified by their GTIN or ISBN instead. Everything else is expected to carry a brand, and placeholder values such as "Generic" or "N/A" count as missing. ### Primary sources - [Google: brand attribute specification](https://support.google.com/merchants/answer/6324351) --- ## condition The condition attribute declares whether an item is new, refurbished or used. Its requirement level is one of the sharpest differences between channels: Google only needs it when an item is not new, while Meta expects it on every item, so a feed built for one and pointed at the other fails on every row. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/condition - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `condition` | | Plain name | Product condition | | Data type | Enumerated value | | Format | One of three lowercase values: new, refurbished, used. Shopify has no native condition field, so for a store that sells anything other than new goods the value has to come from a metafield, a tag convention or a collection membership rule. | #### Example values - `new` - `refurbished` - `used` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required in some cases | `condition` | Required when the item is not new. Assumed new when omitted. | | Meta | Required | `condition` | Required on every item, unlike Google. | | Microsoft Advertising | Optional | `condition` | | | Pinterest | Optional | `condition` | | | OpenAI | Optional | `condition` | One of new, refurbished or used. | ### Frequently asked questions #### Does Google require the condition attribute? Only when the item is not new. If condition is omitted Google assumes new, so a store selling exclusively new goods can leave it out for Google. Meta is different and expects it on every item. #### Where does condition come from in Shopify? Shopify has no built-in condition field. Stores that sell refurbished or used goods typically record it in a product metafield, a tag such as "refurbished", or by collection membership, and the feed maps from whichever convention the store uses. #### What are the valid condition values? Exactly three, written in lowercase: new, refurbished and used. Variations such as "New", "pre-owned" or "second hand" are rejected. ### Primary sources - [Google: condition attribute specification](https://support.google.com/merchants/answer/6324469) --- ## custom_label_0 The custom_label attributes are five free-text fields, numbered 0 to 4, that carry your own segmentation into a feed so campaigns can bid on groupings the channel knows nothing about. Nothing is shown to shoppers: their entire purpose is to let you subdivide a catalogue by margin, season, stock age, bestseller status or anything else you can compute. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/custom-label-0 - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `custom_label_0` | | Plain name | Custom label | | Data type | Text | | Limit | 100 characters | | Format | Free text, but treat it as a controlled vocabulary rather than a description. Values are matched exactly and case-sensitively when building campaign groupings, so a handful of consistent tokens such as high_margin or clearance is far more useful than descriptive prose. Each item may hold one value per label, across five independent labels. | #### Example values - `high_margin` - `clearance` - `aw25` - `bestseller` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Optional | `custom_label_0` | Five labels available, custom_label_0 through custom_label_4. | | Microsoft Advertising | Optional | `custom_label_0` | | | Meta | Optional | `custom_label_0` | Supported for catalogue segmentation in Advantage+ campaigns. | | OpenAI | Optional | `ads_metadata` | There is no custom_label_0. Custom labelling for Ads is carried under ads_metadata instead. | ### Frequently asked questions #### What are custom labels used for? They subdivide a catalogue for campaign management. Because they are invisible to shoppers, they can encode anything useful for bidding: margin band, season, stock age, bestseller status, or whether an item is on promotion. Campaigns then target those groupings directly. #### How many custom labels can I use? Five per item, custom_label_0 through custom_label_4, each holding one value. They are independent, so one can carry margin band while another carries season. #### How do I derive custom labels from Shopify? They are almost always computed rather than stored. Typical sources are a conditional rule on price or margin, a metafield, tag membership, collection membership, or a comparison between cost and price. Anything you can evaluate per variant at generation time can become a label. ### Primary sources - [Google: custom_label attribute specification](https://support.google.com/merchants/answer/6324473) --- ## description The description attribute is the prose a channel uses to understand and display what a product is. On Shopify it maps from body_html, which is rich text, so it needs its HTML stripped and its entities decoded before it can be submitted. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/description - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `description` | | Plain name | Product description | | Data type | Text | | Limit | 5,000 characters | | Format | Plain text. No HTML tags, no promotional wording, no links, and no block capitals. Line breaks are permitted. Because the source is Shopify's rich text editor, decoding HTML entities matters as much as stripping tags: an ampersand left as & shows up literally in the listing. | #### Example values - `A midweight merino crew neck knitted in a 12-gauge rib. Machine washable at 30 degrees.` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required | `description` | HTML is rejected. Only the first 150 to 500 characters typically display. | | Meta | Required | `description` | | | Microsoft Advertising | Required | `description` | | | Pinterest | Required | `description` | | | OpenAI | Required | `description` | Capped at 5,000 characters of plain text. | ### Frequently asked questions #### Why is HTML showing up in my Google Shopping description? Shopify stores product descriptions in body_html, which contains markup from the rich text editor. Mapped straight into the feed, that markup either gets rejected or rendered literally. The field needs the tags stripped and HTML entities decoded before submission. #### How long should a feed description be? The limit is 5,000 characters, but only the first few hundred are typically displayed. Lead with what the product actually is and its defining attributes, and keep specification detail further down where it still contributes to matching without cluttering the listing. #### Can the description be the same for every variant? Yes, and it usually should be. The description describes the product; the variant's distinguishing attributes belong in title, size, colour and the dedicated variant fields. ### Primary sources - [Google: description attribute specification](https://support.google.com/merchants/answer/6324468) --- ## google_product_category The google_product_category attribute places an item in Google's own fixed product taxonomy, a tree of several thousand predefined categories. Google infers a category when the attribute is omitted, but the inference is often wrong for specialist catalogues, and certain categories such as alcohol and subscription hardware require it explicitly. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/google-product-category - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `google_product_category` | | Plain name | Google product category | | Data type | Enumerated value | | Limit | A taxonomy ID, or a full category path | | Format | Either the numeric category ID or the complete path from the Google product taxonomy, with levels separated by " > ". Invented categories are rejected: the value must be an exact match for a node in Google's published taxonomy. Only one category per item is permitted, and it should be the most specific one that genuinely fits. | #### Example values - `212` - `Apparel & Accessories > Clothing > Shirts & Tops` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Recommended | `google_product_category` | Required for some categories including alcohol and subscription hardware. | | Microsoft Advertising | Recommended | `google_product_category` | Microsoft accepts the Google taxonomy values unchanged. | | Meta | Not supported | `google_product_category` | Meta maintains its own category taxonomy instead. | | OpenAI | Optional | `product_category` | Called product_category, and it is your own path from broad to specific separated by >, not Google's numeric taxonomy. The Google values do not carry over. | ### Frequently asked questions #### Do I have to set google_product_category? Not for most products, because Google infers a category from your other attributes. It is required for specific categories including alcoholic beverages and mobile devices or software sold on a contract, and it is worth setting explicitly wherever the inference gets specialist products wrong. #### Can I use my Shopify product type as the category? Not directly. The value has to match Google's published taxonomy exactly, and Shopify product types are free text. The usual approach is a lookup table mapping each product type or collection to the correct taxonomy value. #### Should I use the numeric ID or the full path? Both are accepted. Numeric IDs are shorter and immune to wording changes in the taxonomy; full paths are far easier to read when someone is auditing a mapping. Consistency within a feed matters more than the choice. ### Primary sources - [Google: google_product_category attribute specification](https://support.google.com/merchants/answer/6324436) - [Google product taxonomy](https://support.google.com/merchants/answer/1705911) --- ## gtin The gtin attribute is the globally unique barcode number a manufacturer assigns to a product, covering UPC, EAN, JAN and ISBN formats. Channels use it to match an item against the same product sold elsewhere, and on Shopify it comes from the variant's barcode field, which is optional in the admin and therefore frequently empty. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/gtin - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `gtin` | | Plain name | Global Trade Item Number | | Data type | Text | | Limit | 8, 12, 13 or 14 digits | | Format | Digits only, with no spaces, hyphens or other punctuation. Valid lengths are 8 (EAN-8), 12 (UPC-A), 13 (EAN-13, ISBN-13, JAN) and 14 (ITF-14). The final digit is a check digit calculated from the others, so a transposed digit produces a structurally invalid value that channels reject. Leading zeros are significant and must be preserved. | #### Example values - `00012345600012` - `9781234567897` - `012345678905` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required in some cases | `gtin` | Required when the manufacturer has assigned one. Omit for genuinely custom goods. | | Meta | Recommended | `gtin` | Improves matching and is required for some Shops surfaces. | | Microsoft Advertising | Required in some cases | `gtin` | Expected together with brand unless mpn is supplied. | | Pinterest | Optional | `gtin` | | | OpenAI | Optional | `gtin` | Exactly 8, 12, 13 or 14 digits including a valid check digit. Carry it as a string so leading zeros survive. | ### Frequently asked questions #### Where does the GTIN come from in Shopify? It comes from the barcode field on each variant, under inventory in the variant editor. It is optional in Shopify and not validated there, so it is commonly blank, padded with spaces, or filled with an internal reference that is not a real GTIN. #### What are the valid GTIN lengths? Eight digits for EAN-8, twelve for UPC-A, thirteen for EAN-13, ISBN-13 and JAN, and fourteen for ITF-14. Anything else is structurally invalid. Leading zeros count towards the length and must not be stripped. #### What if my products genuinely have no GTIN? For custom, handmade or own-brand goods with no manufacturer-assigned barcode, supply brand and mpn instead, or set identifier_exists to no. That is a legitimate declaration. Using it to paper over barcodes you simply have not entered causes matching problems rather than solving them. #### Why do my GTINs lose their leading zeros? Almost always a spreadsheet. Opening a feed in Excel or Sheets coerces a numeric-looking string to a number and discards leading zeros, turning a valid twelve-digit UPC into eleven digits. Generating the feed directly from the catalogue avoids the round trip entirely. ### Primary sources - [Google: gtin attribute specification](https://support.google.com/merchants/answer/6324461) - [GS1: GTIN formats and check digit calculation](https://www.gs1.org/services/check-digit-calculator) --- ## id The id attribute is the unique, permanent identifier a sales channel uses to recognise one item across every feed update. In a Shopify feed it must identify a single variant rather than a product, and once a channel has seen an id it should never be reused for a different item. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/id - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `id` | | Plain name | Product ID | | Data type | Text | | Limit | 50 characters | | Format | Unique within the feed, stable across regenerations, and made only of valid Unicode characters. Leading and trailing whitespace is not allowed. The value is case-sensitive on most channels, so treat SKU-1 and sku-1 as different items. | #### Example values - `shopify-44219834` - `TSHIRT-BLK-M` - `9781234567897` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required | `id` | Cannot be changed without losing the item's performance history. | | Meta | Required | `id` | Called "Content ID" in Commerce Manager and in pixel events. | | Microsoft Advertising | Required | `id` | | | Pinterest | Required | `id` | | | OpenAI | Required | `item_id` | Called item_id. Variants are submitted as one row each, so every variant carries its own item_id. | ### Frequently asked questions #### Should the feed id be the Shopify product ID or the variant ID? It should be the variant ID. Channels treat each row as a separately purchasable item, and a Shopify product with three sizes is three items. Using the product ID collapses all variants into one row and loses the size and colour a shopper filters on. #### What happens if I change a product's id in the feed? The channel treats it as a brand new item. The old id is dropped from the catalogue and the new one starts from zero, losing accumulated performance history and any review or ranking signals attached to it. Ids should be treated as permanent. #### Can I use the SKU as the feed id? Yes, provided every variant has a SKU, the SKUs are unique across the whole catalogue, and they never get edited. In practice Shopify SKUs are frequently blank or reused, so the variant ID is the safer source. ### Primary sources - [Google: id attribute specification](https://support.google.com/merchants/answer/6324405) --- ## identifier_exists The identifier_exists attribute is an explicit declaration that a product has no manufacturer-assigned identifiers, set to no when a GTIN, MPN and brand genuinely do not exist for the item. It is a statement about the product, not a way to silence identifier warnings, and channels treat misuse as a data quality problem rather than a resolution. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/identifier-exists - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `identifier_exists` | | Plain name | Identifier exists declaration | | Data type | Boolean | | Format | Accepts yes or no, with yes being the default when the attribute is omitted. Set it to no only for custom, handmade, vintage or one-off goods where no manufacturer has assigned an identifier. When set to no, the channel stops expecting gtin and mpn for that item. | #### Example values - `no` - `yes` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Optional | `identifier_exists` | Defaults to yes. Set to no only for genuinely unidentified products. | | Microsoft Advertising | Optional | `identifier_exists` | | | OpenAI | Not supported | `identifier_exists` | No equivalent. OpenAI treats gtin and mpn as optional attributes rather than asking you to declare their absence. | ### Frequently asked questions #### Can I set identifier_exists to no to stop GTIN warnings? You can, but it is the wrong fix when the products do have barcodes you simply have not entered. The declaration tells the channel not to expect identifiers, which removes the item from identifier-based matching and generally reduces how well it competes. Use it only when the product genuinely has no manufacturer identifier. #### When is identifier_exists set to no legitimate? For custom, handmade, made-to-order, vintage and one-off products, and for own-brand goods where no GTIN has ever been assigned. These are exactly the cases the attribute exists for. #### What happens if I omit identifier_exists? It defaults to yes, meaning the channel expects the item to have identifiers and will warn or disapprove when gtin and mpn are missing for products it believes should have them. ### Primary sources - [Google: identifier_exists attribute specification](https://support.google.com/merchants/answer/6324478) --- ## image_link The image_link attribute is the URL of the single main image a channel shows for an item. On Shopify the correct source is the variant's own image where one is assigned, falling back to the product's featured image, because a feed that shows the wrong colour is worse than one that shows a generic shot. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/image-link - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `image_link` | | Plain name | Main product image URL | | Data type | URL | | Format | A fully qualified URL to a directly accessible image file, served without login, and stable enough to be re-crawled. Channels require a minimum resolution and reject placeholder or watermarked images, promotional overlays, and images that are mostly text or borders. | #### Example values - `https://cdn.shopify.com/s/files/1/0001/products/merino-navy.jpg` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required | `image_link` | Promotional overlays and watermarks cause disapproval. | | Meta | Required | `image_link` | Shops surfaces enforce stricter resolution than catalogue ads. | | Microsoft Advertising | Required | `image_link` | | | Pinterest | Required | `image_link` | The image is the listing. Vertical crops perform materially better. | | OpenAI | Required | `image_url` | Called image_url, and must be a direct JPEG or PNG that is publicly reachable, HTTPS preferred. Further views go in additional_image_urls. | ### Frequently asked questions #### Should image_link use the variant image or the product image? The variant image where one is assigned, falling back to the product's featured image. If every colour of a jumper shows the navy photograph, shoppers click through expecting navy and bounce, which hurts both conversion and the listing's quality signals. #### Why was my image rejected even though the URL works? Channels reject images that carry promotional overlays, watermarks, borders or text, fall below a minimum resolution, or are obvious placeholders. The URL resolving is necessary but not sufficient; the image content itself has to meet the policy. #### Can I add more than one image? Yes, through additional_image_link, which most channels support alongside the single main image_link. The main image is the one shown in the listing, and the additional images appear on the product detail surface. ### Primary sources - [Google: image_link attribute specification](https://support.google.com/merchants/answer/6324350) - [Google: Image guidelines](https://support.google.com/merchants/answer/6324350) --- ## item_group_id The item_group_id attribute groups the rows that are variants of the same product, so a channel can show one listing with a size or colour picker instead of several unrelated listings. On Shopify the natural source is the product ID, shared by every variant row generated from that product. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/item-group-id - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `item_group_id` | | Plain name | Item group ID | | Data type | Text | | Limit | 50 characters | | Format | Any stable string, identical across every variant of one product and different for every other product. It must be as permanent as the id attribute: changing it regroups the listing and resets the grouping's history. Products with a single variant may omit it. | #### Example values - `shopify-7891234` - `MERINO-CREW` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required in some cases | `item_group_id` | Required when a product has variants. Must accompany the variant attributes. | | Meta | Required in some cases | `item_group_id` | Drives the variant picker in Shops. | | Microsoft Advertising | Required in some cases | `item_group_id` | | | Pinterest | Optional | `item_group_id` | | | OpenAI | Optional | `group_id` | Called group_id rather than item_group_id, and it travels with listing_has_variations set true and a variant_dict mapping option names to values. A Google mapping has to be rebuilt here, not renamed. | ### Frequently asked questions #### What is the difference between id and item_group_id? id is unique to one purchasable variant; item_group_id is shared by every variant of the same product. In a Shopify feed, id comes from the variant and item_group_id comes from the product, so a jumper in three sizes produces three ids and one item_group_id. #### What happens if item_group_id is missing? Each variant appears as an independent listing. Shoppers see the same jumper three times with no way to switch size, the listings compete against each other, and variant-level features such as the colour picker in Shops do not appear. #### Do single-variant products need an item_group_id? No. A product with one variant has nothing to group, so the attribute can be omitted. It does no harm to include it consistently, which keeps the mapping simpler than special-casing. ### Primary sources - [Google: item_group_id attribute specification](https://support.google.com/merchants/answer/6324507) --- ## link The link attribute is the URL a shopper is sent to after clicking a listing, and the page the channel crawls to verify that the feed is telling the truth about price and availability. On a multi-market Shopify store it must point at the market-specific storefront URL, not the primary domain. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/link - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `link` | | Plain name | Product landing page URL | | Data type | URL | | Format | Must be a fully qualified URL starting with http:// or https://, on a domain verified and claimed in the channel's account. It must resolve directly to the product page without an interstitial, and for a variant it should carry the variant selector so the right option is preselected. | #### Example values - `https://example.com/products/merino-crew?variant=44219834` - `https://example.de/products/merino-crew?variant=44219834` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required | `link` | Crawled and compared against feed price and availability. | | Meta | Required | `link` | | | Microsoft Advertising | Required | `link` | | | Pinterest | Required | `link` | | | OpenAI | Required | `url` | Called url. This is the page a ChatGPT shopper lands on, so it has to resolve for a logged-out visitor. | ### Frequently asked questions #### Should the product link include the Shopify variant parameter? Yes. Without ?variant= the shopper lands on the product's default variant, which may be a different size or colour and often a different price than the listing they clicked. That mismatch also makes it harder for the channel's crawler to confirm the feed is accurate. #### Which URL should a multi-market Shopify store submit? The storefront URL for the market that feed targets, so a shopper sees prices in their own currency and the crawler sees the same price the feed declares. Submitting the primary domain for every market is a common cause of price mismatch disapprovals. #### Does the link domain have to be verified? Yes. Channels require the domain to be claimed and verified in the merchant account, and reject links pointing anywhere else, including URL shorteners and tracking redirects that leave the verified domain. ### Primary sources - [Google: link attribute specification](https://support.google.com/merchants/answer/6324416) --- ## mpn The mpn attribute is the part number a manufacturer uses to identify a product in its own catalogue, and it serves as the fallback identifier when an item has no GTIN. Unlike a GTIN it is only unique within one manufacturer, so it is meaningless to a channel unless the brand attribute is supplied alongside it. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/mpn - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `mpn` | | Plain name | Manufacturer Part Number | | Data type | Text | | Limit | 70 characters | | Format | Alphanumeric, as published by the manufacturer, including any hyphens or letters it contains. It is not your internal SKU unless your company is also the manufacturer. Submitting a retailer-invented code is a common and damaging mistake, because it prevents the channel matching the item to the same product elsewhere. | #### Example values - `GX-1140-BLK` - `4056744` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required in some cases | `mpn` | Required when the product has no GTIN. Must be paired with brand. | | Meta | Optional | `mpn` | | | Microsoft Advertising | Required in some cases | `mpn` | Accepted in place of gtin, together with brand. | | Pinterest | Optional | `mpn` | | | OpenAI | Optional | `mpn` | Manufacturer part number, accepted as an alternative identifier to gtin. | ### Frequently asked questions #### Is the MPN the same as my Shopify SKU? Only if you manufacture the product yourself. A SKU is a code you invent for your own stock control; an MPN is the code the manufacturer publishes. Submitting your internal SKU as the MPN stops the channel matching your item to the same product sold elsewhere. #### What is the difference between GTIN and MPN? A GTIN is globally unique and identifies the product no matter who sells it. An MPN is only unique within one manufacturer's catalogue, which is why it has to be submitted together with brand to mean anything. #### Do I need both gtin and mpn? Not usually. Where a GTIN exists it is the stronger identifier and is generally sufficient. MPN matters when there is no GTIN, and supplying both does no harm when you have accurate values for each. ### Primary sources - [Google: mpn attribute specification](https://support.google.com/merchants/answer/6324482) --- ## price The price attribute is the item's full price before any discount, submitted as a number followed by an ISO 4217 currency code. It must match what a shopper sees on the landing page in that market, including whether tax is included, which is why multi-market Shopify stores so often trip the price mismatch disapproval. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/price - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `price` | | Plain name | Product price | | Data type | Price (number and currency) | | Format | A number and a currency code separated by a space, using a full stop as the decimal separator and no thousands separator, for example "15.00 GBP". The currency must match the market the feed targets. Whether the figure includes tax depends on the destination country's convention, not on a setting in the feed. | #### Example values - `15.00 GBP` - `199.99 USD` - `24.90 EUR` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required | `price` | Crawled from the landing page and compared. Must be the pre-discount price. | | Meta | Required | `price` | | | Microsoft Advertising | Required | `price` | | | Pinterest | Required | `price` | | | OpenAI | Required | `price` | A single string of amount, space, uppercase ISO 4217 code, so "79.99 USD" rather than an amount and a currency in separate fields. | ### Frequently asked questions #### Should the price attribute include tax? It depends on the destination country's convention. Markets that display tax-inclusive pricing to consumers expect the feed to match, and markets that display tax separately expect the pre-tax figure. The rule that matters is that the feed and the landing page agree for that market. #### Should price be the discounted price or the full price? price is the full, pre-discount price. A discount goes in sale_price, which lets the channel display a strikethrough and show the item as reduced. Putting the discounted figure in price loses that treatment and misstates the reference price. #### Why does Google say my price does not match my website? The most common causes on Shopify are a stale feed that has not regenerated since a price change, a link pointing at the primary domain instead of the market storefront so the crawler sees a different currency, and a tax convention mismatch between the feed and the displayed price. ### Primary sources - [Google: price attribute specification](https://support.google.com/merchants/answer/6324371) --- ## sale_price The sale_price attribute is the temporarily reduced price of an item, submitted alongside the full price so a channel can show the reduction rather than simply a lower number. On Shopify the reliable source is the variant's compare-at price relationship, and discounts applied by code or automatic rule are not visible in the product record at all. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/sale-price - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `sale_price` | | Plain name | Sale price | | Data type | Price (number and currency) | | Format | Same format as price: a number, a space, then an ISO 4217 currency code. It must be lower than price and use the same currency. Some channels additionally expect an effective date range through sale_price_effective_date when the reduction is time-limited. | #### Example values - `11.25 GBP` - `149.99 USD` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Optional | `sale_price` | Must be lower than price. Pair with sale_price_effective_date when time-limited. | | Meta | Optional | `sale_price` | | | Microsoft Advertising | Optional | `sale_price` | | | Pinterest | Optional | `sale_price` | | | OpenAI | Optional | `sale_price` | Must be greater than zero and strictly less than price, so an empty or equal sale price has to be dropped rather than sent. | ### Frequently asked questions #### How does Shopify's compare-at price map to a feed? When a Shopify variant has a compare-at price higher than its price, the compare-at figure is the reference price and the current price is the reduced one. In feed terms that means price takes the compare-at value and sale_price takes the current price. #### Why do my discount codes not show up in the feed? Shopify discount codes and automatic discounts are applied at checkout, not stored on the product, so nothing in the product record reflects them. Getting them into a feed requires reading the discount rules separately and resolving which ones apply to each product at generation time. #### What happens if sale_price is higher than price? The channel rejects the sale price, and depending on the channel either ignores it or disapproves the item. sale_price must always be strictly lower than price and in the same currency. ### Primary sources - [Google: sale_price attribute specification](https://support.google.com/merchants/answer/6324471) --- ## title The title attribute is the product name a channel shows in a listing and matches against search queries, which makes it the single highest-leverage field in a product feed. On Shopify it usually needs building from the product title plus the variant's options, because the raw product title alone does not describe an individual variant. - **Canonical URL:** https://www.feedlab.io/knowledge/attributes/title - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Specification | Property | Value | | --- | --- | | Attribute | `title` | | Plain name | Product title | | Data type | Text | | Limit | 150 characters | | Format | Plain text only, no HTML, no promotional wording such as "free shipping" or "sale", and no block capitals. Channels truncate well before the limit in most placements, so the distinguishing words belong in the first 60 to 70 characters. | #### Example values - `Northwind Merino Crew Neck Jumper, Navy, Medium` - `Acme Cast Iron Skillet 26cm` #### Support by channel | Channel | Requirement | Field name | Notes | | --- | --- | --- | --- | | Google Shopping | Required | `title` | Matched against search queries. Promotional text causes disapproval. | | Meta | Required | `title` | Limit is shorter in practice for Shops surfaces. | | Microsoft Advertising | Required | `title` | | | Pinterest | Required | `title` | | | OpenAI | Required | `title` | Capped at 150 characters of plain text. | ### Frequently asked questions #### How long should a product feed title be? The specification allows up to 150 characters, but listings truncate far earlier, so the words that distinguish the product should appear within the first 60 to 70 characters. A common structure is brand, then product name, then the attributes that define the variant. #### Why does my Shopify title not include the size and colour? Shopify stores the product title once and the options separately on each variant. A feed that maps the product title directly gives every variant the same name, so the size and colour have to be appended from the variant's option values. #### Can I put "free shipping" or "sale" in the title? No. Channels treat promotional text in the title as a policy violation and disapprove the item. Promotions belong in dedicated attributes such as sale_price or a promotion identifier. ### Primary sources - [Google: title attribute specification](https://support.google.com/merchants/answer/6324415) --- # Feed errors ## Duplicate product id A duplicate id error means two or more rows in a feed claim the same identifier, so the channel cannot tell which one describes the item and keeps only the last it processed. On Shopify it usually means the feed was built per product rather than per variant, or that a SKU used as the id is blank or reused across variants. - **Canonical URL:** https://www.feedlab.io/knowledge/errors/duplicate-id - **Last reviewed:** 17 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### How this appears in your account - `Duplicate value: id [id]` - `Duplicate products` - `Two or more items share the same id` ### Why it happens on Shopify #### A SKU was used as the id and is not unique Shopify does not enforce unique SKUs, and does not require them at all. Mapping id from SKU produces collisions wherever two variants share one, and empty ids wherever the field is blank. #### The feed rows out at product level When the feed emits one row per product but the mapping expands variants, several rows inherit the same product-derived id. Variant-level rows need variant-level ids. #### The same product appears through two collections Feeds assembled by iterating collections emit a product once per collection it belongs to. A product in both "New in" and "Knitwear" produces two identical rows. #### A prefix was added inconsistently Where ids are built by concatenating a prefix with an identifier, a missing or conditional prefix can make two genuinely different variants collapse to the same string. ### How to fix it 1. **Find which ids collide** — Group the feed by id and list any value appearing more than once. The pattern in the duplicates almost always names the cause: whole products repeating points at collection iteration, while pairs of variants point at SKU reuse. 2. **Map id from the variant identifier** — Shopify variant IDs are unique, permanent and always present, which makes them the correct source. SKUs are none of those three unless your store enforces them rigorously. 3. **Deduplicate before rendering rows** — If the feed is assembled by walking collections, collect the product set first and render each product once, rather than emitting a row per collection membership. 4. **Separate id from item_group_id** — Variants of one product are supposed to share an item_group_id while each keeps its own id. Collapsing the two is what turns a correct grouping into a duplicate error. 5. **Fail the run rather than publishing duplicates** — A uniqueness check at generation time catches the problem before the channel does, which matters because a duplicate id silently drops products from the catalogue rather than reporting each one. ### How feedlab handles this feedlab expands one product into a row per variant and sources id from the variant, so uniqueness holds by construction rather than by convention. Where a store needs its own id scheme, the resolved values are visible in the preview against real products before the feed goes out. ### Frequently asked questions #### Why are duplicate ids a problem if the products are identical? Because the channel keeps only one row per id and discards the others. Where the duplicates describe genuinely different variants, the ones dropped disappear from the catalogue silently rather than being reported individually. #### Can I use the Shopify SKU as the feed id? Only if every variant has one, they are unique catalogue-wide, and they never change. Shopify enforces none of those conditions, so the variant ID is the safer source. #### What is the difference between a duplicate id and item_group_id? Variants of the same product are meant to share an item_group_id while each keeps a unique id. Giving them the same id instead is the error; giving them the same item_group_id is the intended behaviour. ### Primary sources - [Google: id attribute specification](https://support.google.com/merchants/answer/6324405) --- ## Image link cannot be crawled An image crawl failure means the channel could not retrieve the file at image_link, or retrieved it and judged it unusable. On Shopify the URL itself is rarely wrong, so the cause is usually a rate limit hit during a first full crawl, a temporary CDN response, or an image whose content breaks policy rather than one that is genuinely missing. - **Canonical URL:** https://www.feedlab.io/knowledge/errors/image-crawl-failure - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### How this appears in your account - `Invalid value: image link [image_link]` - `Image not found or cannot be crawled [image_link]` - `Generic image` - `Promotional overlay on image [image_link]` ### Why it happens on Shopify #### The crawler was rate limited The first crawl of a large new feed requests every image in a short window. Where the CDN or a security layer throttles that burst, images fail to fetch even though each URL works perfectly when opened by hand. #### The image carries a promotional overlay or watermark Badges such as "Sale" or "50% off" burned into the image, watermarks, borders and added text all breach image policy. The file is fetched successfully and then rejected on its content. #### The URL changed after the feed was generated Replacing a product image in Shopify produces a new CDN URL. A feed still carrying the old URL points at a file that no longer resolves. #### The image is a placeholder Themes and import tools substitute a "no image available" graphic when a product has none. It fetches correctly and is rejected as a generic image. #### The image is below the minimum resolution Channels enforce minimum pixel dimensions, and the threshold is higher for shopping surfaces than for ads. Thumbnails and legacy images from an old catalogue frequently fall below it. ### How to fix it 1. **Open the failing URL in a private window** — If it loads normally, the file is fine and the problem is either transient or about the image's content. If it does not, the URL is stale and the feed needs regenerating against current image data. 2. **Wait out a first-crawl failure before changing anything** — Large new feeds routinely report image errors on the initial crawl that clear on the next attempt. Re-uploading or re-mapping in response adds churn without fixing anything. 3. **Remove overlays, badges and watermarks** — The main image must show the product alone against a clean background. Promotional messaging belongs in the channel's own fields, which is also where it can be changed without re-editing artwork. 4. **Regenerate the feed so image URLs stay current** — Because replacing an image changes its URL, a feed that regenerates on a schedule from current catalogue data keeps pace automatically, where a static exported file does not. 5. **Map variant images with a product-level fallback** — Prefer the variant's own image, falling back to the product's featured image when a variant has none. This avoids empty image_link values and stops every colour showing the same photograph. ### How feedlab handles this feedlab resolves image_link through a conditional chain, using the variant image where one exists and falling back to the product's featured image, and regenerates from webhook-updated catalogue data so replaced images do not leave stale URLs behind. ### Frequently asked questions #### The image URL works when I open it. Why does the crawler fail? Most often rate limiting during a first full crawl, when every image is requested at once and the CDN throttles the burst. It can also mean the file fetched fine and was rejected on its content, for a watermark, overlay or low resolution. #### Can I use images with a sale badge on them? No. Promotional overlays, badges, watermarks, borders and added text all breach image policy. The main image has to show the product on its own, and promotions belong in the channel's dedicated fields. #### Why did my image URLs stop working after I updated a product? Replacing an image in Shopify generates a new CDN URL rather than overwriting the old one. Any feed still carrying the previous URL points at a file that no longer exists, which a scheduled regeneration resolves. ### Primary sources - [Google: image_link attribute specification](https://support.google.com/merchants/answer/6324350) --- ## Invalid value: GTIN The "Invalid value: GTIN" error means the feed supplied a value in the gtin attribute that is not a structurally valid Global Trade Item Number. Unlike a missing GTIN this is usually a disapproval rather than a warning, because a wrong identifier matches the item to the wrong product rather than to none. - **Canonical URL:** https://www.feedlab.io/knowledge/errors/invalid-value-gtin - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### How this appears in your account - `Invalid value: GTIN [gtin]` - `Invalid GTIN [gtin]` - `Incorrect identifier [gtin]` ### Why it happens on Shopify #### Leading zeros were stripped by a spreadsheet This is the most common cause by a distance. Opening a feed in Excel or Google Sheets coerces a numeric-looking string to a number and discards leading zeros, turning a valid twelve-digit UPC into an invalid eleven-digit value. #### The check digit does not compute The final digit of a GTIN is calculated from the preceding digits. A single transposed or mistyped digit produces a value of the right length that still fails validation, which is exactly what hand-keyed barcodes produce. #### The value is not a GTIN at all The Shopify barcode field is free text with no validation, so it frequently holds an internal SKU, a supplier reference or a shelf code. These are submitted as if they were identifiers and rejected. #### Formatting characters survived into the feed Barcodes copied from supplier documents often carry spaces, hyphens or non-breaking spaces. A GTIN must be digits only, so anything else invalidates it even when the digits themselves are correct. #### A restricted or reserved prefix was used Some number ranges are reserved for internal use within a retailer and are not globally unique. Values in those ranges are structurally well formed but rejected as identifiers because they mean nothing outside your own systems. ### How to fix it 1. **Stop editing feeds in a spreadsheet** — If a feed is exported, opened in Excel or Sheets and re-saved at any point, assume leading zeros are already gone. Generating the feed directly from the catalogue removes the entire class of problem rather than correcting it after the fact. 2. **Validate length before submitting** — A GTIN must be exactly 8, 12, 13 or 14 digits after stripping whitespace. Anything else is invalid by definition and can be caught before the feed is uploaded. 3. **Verify the check digit** — Compute the check digit from the preceding digits and compare it to the last one. This catches transposition and typing errors that a length check cannot, and it is the single most effective validation you can apply to hand-entered barcodes. 4. **Strip formatting rather than trusting the source** — Remove spaces, hyphens and non-breaking spaces, then confirm what remains is digits only. Pad values that are short only where you know the source genuinely dropped leading zeros, never as a blanket rule. 5. **Route failures to the identifier fallback** — When a value fails validation, submitting it anyway guarantees a disapproval. Sending brand and mpn instead, or declaring identifier_exists as no where appropriate, keeps the item serving while the underlying data is corrected. ### How feedlab handles this feedlab validates barcode length and check digit as a transformation step, so an invalid value never reaches the feed. Items that fail fall through to a brand and mpn mapping instead, and the preview lists exactly which products failed validation and why, against your live catalogue. ### Frequently asked questions #### Why did my GTINs lose their leading zeros? A spreadsheet. Excel and Google Sheets treat a numeric-looking string as a number and discard leading zeros on open, so a valid 012345678905 becomes 12345678905. Any workflow that routes a feed through a spreadsheet will keep reintroducing this. #### What is a GTIN check digit? The last digit of a GTIN, calculated from the preceding digits using a weighted sum. Its purpose is to catch typing and scanning errors, which means a mistyped barcode of the correct length still fails validation. #### Is an invalid GTIN worse than a missing one? Generally yes. A missing GTIN is usually a warning about reduced performance, whereas an invalid one is typically a disapproval, because a wrong identifier can match your item to a different product entirely. ### Primary sources - [Google: gtin attribute specification](https://support.google.com/merchants/answer/6324461) - [GS1: Check digit calculator](https://www.gs1.org/services/check-digit-calculator) --- ## Mismatched value: availability The availability mismatch error means the feed says an item is purchasable but the crawled landing page suggests otherwise, or the reverse. On Shopify it is usually a stale feed after a sell-out, or an availability value derived from inventory quantity alone without accounting for variants that do not track inventory or that allow overselling. - **Canonical URL:** https://www.feedlab.io/knowledge/errors/mismatched-value-availability - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### How this appears in your account - `Mismatched value (page crawl): availability [availability]` - `Availability mismatch` - `Product unavailable on landing page` ### Why it happens on Shopify #### The product sold out after the last feed generation The most common case, and a pure timing problem. The feed was accurate when it ran, stock reached zero afterwards, and the crawler compared the live page against a feed describing an earlier moment. #### Availability was derived from quantity alone A variant with zero inventory is still purchasable if it does not track inventory, or if it allows overselling. Mapping in_stock purely on quantity greater than zero marks those items out of stock while the storefront happily sells them. #### The link resolves to a variant that is unavailable If the link omits the variant parameter, the crawler lands on the product's default variant. When that default is sold out but the row describes an in-stock variant, the page and the feed disagree. #### Stock is location-scoped Shopify tracks inventory per location. Summing every location, including warehouses that do not fulfil online orders, overstates what is actually purchasable through the storefront. #### The availability value uses the wrong vocabulary Values must be lowercase with underscores. Older space-separated forms such as "in stock" persist in feed templates and are either rejected or misread. ### How to fix it 1. **Check the failing row against its own link** — Open the link from the row and confirm whether that specific variant is buyable. This separates a timing problem from a logic problem, and the two need completely different fixes. 2. **Derive availability from purchasability, not quantity** — An item is in stock if it does not track inventory, or tracks inventory with quantity above zero, or allows overselling when out of stock. Only a variant that tracks inventory, has none and denies overselling is genuinely out_of_stock. 3. **Scope inventory to the locations that fulfil online orders** — Exclude locations that do not serve the storefront before summing quantity, so the feed reflects what a shopper can actually buy rather than what exists somewhere in the business. 4. **Include the variant parameter in every link** — Adding the variant selector to the landing page URL sends both the shopper and the crawler to the exact variant the row describes, which removes an entire class of false mismatch. 5. **Regenerate often enough to track how fast stock moves** — For a catalogue with fast-moving or low stock, a daily feed will regularly be wrong. Increase the regeneration frequency, and on channels that support it use a push mechanism for urgent stock changes rather than waiting for the next scheduled pull. ### How feedlab handles this feedlab registers Shopify webhooks, so an inventory change lands in feedlab within seconds and every scheduled run reads current data rather than a nightly snapshot. Availability is mapped through conditional rules over the inventory policy and tracking fields rather than raw quantity, and a daily reconciliation pass catches anything a webhook missed. ### Frequently asked questions #### How do I correctly map Shopify availability? Treat it as purchasability rather than quantity. The item is in stock if inventory is not tracked, or tracked with quantity above zero, or the variant allows overselling. Only a tracked variant with no stock that denies overselling is out_of_stock. #### Why do out-of-stock products keep getting disapproved? Usually feed cadence. If stock reaches zero hours after the feed runs, the feed keeps claiming the item is available until the next regeneration, and the crawler can check at any point in that window. #### Does Shopify multi-location inventory affect the feed? Yes. Summing every location counts stock held at warehouses that do not fulfil online orders, which overstates availability. Inventory should be scoped to the locations that actually serve the storefront. ### Primary sources - [Google: availability attribute specification](https://support.google.com/merchants/answer/6324448) - [Google: Mismatched value (page crawl)](https://support.google.com/merchants/answer/6098296) --- ## Mismatched value: price The price mismatch error means the price in the feed does not agree with the price the channel found when it crawled the landing page. On Shopify the three usual culprits are a feed that has not regenerated since a price change, a link pointing at the wrong market's storefront, and a tax convention difference between the feed and the displayed price. - **Canonical URL:** https://www.feedlab.io/knowledge/errors/mismatched-value-price - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### How this appears in your account - `Mismatched value (page crawl): price [price]` - `Price mismatch` - `Value mismatch between feed and landing page: price` ### Why it happens on Shopify #### The feed is stale A feed generated nightly describes the catalogue as it was at generation time. A price edited the following morning leaves the feed wrong until the next run, and the crawler compares against the live page in the meantime. #### The link points at the wrong market On a multi-market Shopify store, submitting the primary domain for every market means the crawler sees prices in the primary market's currency while the feed declares another. The values disagree because they describe different storefronts. #### Tax is included on one side but not the other Markets differ in whether displayed prices include tax. If the feed carries the tax-exclusive figure while the storefront shows tax-inclusive pricing, every item mismatches by exactly the tax rate. #### A discount applies on the storefront but not in the feed Automatic discounts and discount codes are applied by Shopify at checkout or by the theme, and are not stored on the product record. The crawler can see a reduced price the feed knows nothing about. #### The landing page price is rendered by JavaScript If a theme or currency-switching app writes the price into the page after load, a crawler may read a placeholder or the base-currency value rather than the price a shopper sees. ### How to fix it 1. **Compare the feed row against the crawled page directly** — Open the exact link from the failing row and note the price shown, the currency and whether tax is included. Most mismatches become obvious at this point, and it distinguishes a staleness problem from a structural one. 2. **Increase how often the feed regenerates** — If the price on the page is right and the feed is behind, the mapping is fine and the cadence is wrong. Match regeneration frequency to how often prices actually change rather than to a default daily schedule. 3. **Point each market's feed at its own storefront URL** — Generate one feed per market and map link to that market's domain, so the crawler sees the same currency and price the feed declares. This resolves mismatches that no amount of regeneration would fix. 4. **Align the tax convention with the destination market** — Confirm whether the destination country expects tax-inclusive or tax-exclusive prices and make the feed match what the storefront displays for that market. An error identical across every item usually means this is the cause. 5. **Model storefront discounts in the feed** — Where an automatic discount reduces the price a shopper sees, the reduction has to reach the feed as sale_price. Resolving which discounts apply to which products at generation time keeps the two sides consistent. ### How feedlab handles this feedlab regenerates on a cron schedule from webhook-updated catalogue data, so a price change in Shopify reaches the next run rather than waiting for a full resync. Market-aware storefront URLs are generated per locale, and product- and collection-scoped discount codes are indexed per product and offered as a mapping source for sale_price. ### Frequently asked questions #### How quickly must a feed reflect a price change? There is no published grace period, and the crawler can revisit a page at any time. In practice the risk of disapproval scales with how long the feed stays wrong, so the regeneration cadence should track how often prices actually change in your store. #### Why does every single product show a price mismatch? A mismatch on the entire catalogue at once points at something structural rather than stale data. The two usual causes are a tax convention difference between the feed and the storefront, and links pointing at a different market than the feed describes. #### Do Shopify discount codes cause price mismatches? They can. Automatic discounts that reduce the displayed price are applied outside the product record, so the crawler sees a lower price than the feed declares. The discount has to be resolved per product and mapped to sale_price for the two to agree. ### Primary sources - [Google: price attribute specification](https://support.google.com/merchants/answer/6324371) - [Google: Mismatched value (page crawl)](https://support.google.com/merchants/answer/6098296) --- ## Missing value: brand The "Missing value: brand" issue means a channel expects a brand name for a product and the feed did not supply one, or supplied a placeholder it treats as empty. On Shopify it usually means the vendor field is blank, or holds a supplier or internal category rather than the brand a shopper would recognise. - **Canonical URL:** https://www.feedlab.io/knowledge/errors/missing-value-brand - **Last reviewed:** 17 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### How this appears in your account - `Missing value: brand [brand]` - `Missing brand` - `Limited performance due to missing identifiers [brand]` ### Why it happens on Shopify #### The Shopify vendor field is empty Vendor is optional and is frequently left blank on products created by hand or imported without a brand column, which leaves the mapped brand attribute empty. #### Vendor holds a supplier rather than a brand Many stores use vendor operationally, recording the wholesaler or distributor they buy from. That value is real but it is not the brand, so it either misidentifies the product or is rejected as meaningless. #### A placeholder was submitted Values such as "Generic", "N/A", "None", "Unbranded" or a single hyphen are treated as missing rather than as a declaration that no brand exists. #### The product genuinely has no brand Unbranded commodity goods and some handmade items have no brand in any meaningful sense, and the feed needs to say so through identifier_exists rather than inventing a value. ### How to fix it 1. **Audit what the vendor field actually contains** — List the distinct vendor values across the catalogue. This is usually a short list, and it immediately shows whether vendor holds real brands, supplier names, or a mixture of both. 2. **Map vendor values to real brand names** — Where vendor holds supplier names, a lookup table translating each one to the correct consumer-facing brand is more reliable than mapping the field directly, and it keeps working as products are added. 3. **Use a metafield where the brand is recorded properly** — Stores that already track brand in a product metafield should map from there instead. It is purpose-built for the job and not subject to operational reuse the way vendor is. 4. **Use your own brand for own-brand goods** — Products you manufacture or sell under your own label should carry your store's brand name. This is correct rather than a workaround, and it makes the items eligible for brand-based matching. 5. **Declare identifier_exists as no for genuinely unbranded products** — For commodity goods with no brand and no manufacturer identifier, the honest declaration is identifier_exists set to no. Never substitute a placeholder such as "Generic", which counts as missing anyway. ### How feedlab handles this feedlab can source brand from the vendor field, a metafield, a static value, or a lookup table that translates vendor values into brand names, with a conditional fallback chain across them. The preview resolves the mapping against real products, so gaps show up before the feed is published rather than in Merchant Center afterwards. ### Frequently asked questions #### Can I put "Generic" in the brand field? No. Channels treat Generic, N/A, None, Unbranded and similar placeholders as missing values, so it does not resolve the issue. For products that genuinely have no brand, declare identifier_exists as no instead. #### Should brand come from the Shopify vendor field? Only if vendor actually holds brand names in your store. It is free text with no validation and is commonly used for suppliers or internal groupings, so audit the distinct values before mapping it directly. #### Which products are exempt from needing a brand? Google exempts movies, books and musical recordings, which are identified by GTIN or ISBN. Everything else is expected to carry a brand where one exists. ### Primary sources - [Google: brand attribute specification](https://support.google.com/merchants/answer/6324351) --- ## Missing value: GTIN The "Missing value: GTIN" issue means a channel believes a product has a manufacturer-assigned barcode but the feed did not supply one. On Shopify it almost always traces back to the variant barcode field, which is optional in the admin and therefore left empty across large parts of most catalogues. - **Canonical URL:** https://www.feedlab.io/knowledge/errors/missing-value-gtin - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### How this appears in your account - `Missing value: GTIN [gtin]` - `Missing GTIN` - `Limited performance due to missing identifiers [gtin, mpn, brand]` ### Why it happens on Shopify #### The variant barcode field is empty in Shopify Barcode is optional on a Shopify variant and is not validated, so it is commonly blank on products imported from a supplier sheet, created by hand, or migrated from another platform. An empty barcode produces an empty gtin column. #### The barcode holds something that is not a GTIN Merchants often reuse the barcode field for an internal reference, a supplier code or a shelf location. The feed then submits a value that is structurally not a GTIN, which reads as missing or invalid rather than as a real identifier. #### The product genuinely has no GTIN but nothing says so Custom, handmade and own-brand goods have no manufacturer-assigned barcode. Without an explicit identifier_exists declaration, the channel assumes the identifier should exist and reports it as missing. #### The barcode sits on the product rather than the variant Feeds built at product level rather than variant level have nowhere to read a per-variant barcode from, so every row inherits one value or none. Each size and colour has its own GTIN in reality. ### How to fix it 1. **Establish how much of the catalogue is actually affected** — Before changing anything, count the variants with an empty or non-numeric barcode. The fix is different for a handful of products than for a supplier category with thousands, and the count tells you which problem you have. 2. **Fill in the barcodes that exist** — For branded retail goods the GTIN is printed on the packaging and is usually available from the supplier as a bulk list. Import it into the variant barcode field. This is the only fix that genuinely resolves the issue for products that have identifiers. 3. **Supply brand and mpn where there is no GTIN but the manufacturer is known** — Channels accept the combination of brand and manufacturer part number in place of a GTIN. Map brand from a reliable source rather than assuming the Shopify vendor field is correct, and use the manufacturer's published part number rather than your own SKU. 4. **Declare identifier_exists as no for genuinely unidentified products** — For custom, handmade, vintage and made-to-order goods, set identifier_exists to no. This is a legitimate declaration for those items. Applying it across the whole catalogue to silence the warning removes your products from identifier-based matching and usually costs more than the warning does. 5. **Validate at generation time rather than after upload** — Check the barcode is 8, 12, 13 or 14 digits and passes its check digit before it reaches the feed. Routing invalid values to the brand and mpn fallback instead of submitting them keeps the feed clean without hiding the underlying data gap. ### How feedlab handles this feedlab treats identifiers as a conditional chain rather than a single mapping: use the variant barcode when it is structurally valid, fall back to brand and mpn when it is not, and set identifier_exists only for the products that genuinely have no identifier. The rule preview shows which products land in each branch against your real catalogue before the feed is published. ### Frequently asked questions #### Will missing GTINs get my products disapproved? Usually not on its own. Missing identifiers are typically reported as a warning describing limited performance rather than a disapproval, so the items still serve but compete less effectively because the channel cannot match them to the same product elsewhere. #### Can I just set identifier_exists to no for everything? It silences the warning but it is the wrong fix for products that do have barcodes. The declaration removes items from identifier-based matching, which generally costs more performance than the original warning. Reserve it for genuinely unidentified goods. #### Where is the GTIN stored in Shopify? In the barcode field on each variant, under inventory in the variant editor. It is per variant rather than per product, because each size and colour has its own barcode in reality. ### Primary sources - [Google: gtin attribute specification](https://support.google.com/merchants/answer/6324461) - [Google: Unique product identifiers](https://support.google.com/merchants/answer/160161) --- # Concepts ## Feed scheduling Feed scheduling is the arrangement where a sales channel fetches a feed from a fixed URL on a repeating timetable instead of the merchant uploading a file. It matters because the interval between regenerations is the window in which a channel can be wrong about a price or a stock level, and most mismatch disapprovals are caused by that window being too wide rather than by a bad mapping. - **Canonical URL:** https://www.feedlab.io/knowledge/concepts/feed-scheduling - **Last reviewed:** 17 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Frequently asked questions #### How often should a product feed be regenerated? Often enough that the feed is rarely wrong. If prices change weekly and stock is deep, daily is comfortable. If stock is shallow and moves quickly, or prices change through the day, daily guarantees windows where the feed contradicts the site and disapprovals follow. #### What is the difference between regenerating a feed and the channel fetching it? Two independent clocks. You control when the file is rebuilt from the catalogue; the channel controls when it reads the URL. Accuracy depends on both, and a feed regenerated just after the channel fetched it is stale for almost a full cycle. #### How often do the major channels fetch a scheduled feed? Google Merchant Center offers daily, weekly or monthly. Meta supports hourly, daily or weekly. Pinterest fetches once every 24 hours. Microsoft runs on a daily cadence. Meta is the only one of the four offering sub-daily scheduled fetches. #### Why does feed cadence cause disapprovals? Because channels crawl the landing page and compare it against the feed. Any gap between a change in the shop and the next regeneration is a window where the two disagree, and a crawl landing in that window produces a price or availability mismatch. ### Primary sources - [Google: Scheduled fetches](https://support.google.com/merchants/answer/1219255) --- ## Product feed A product feed is a structured file listing every item a shop sells, with one row per purchasable item and one column per attribute the destination understands. Sales channels use it to build their own copy of a catalogue, which is why a feed is a translation of a shop's data into another system's schema rather than an export of it. - **Canonical URL:** https://www.feedlab.io/knowledge/concepts/product-feed - **Last reviewed:** 17 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Frequently asked questions #### What is a product feed? A structured file listing every item a shop sells, with one row per purchasable item and one column per attribute the destination understands. Channels such as Google Shopping and Meta read it to build their own copy of the catalogue, then keep that copy in step by fetching the file again on a schedule. #### What file formats can a product feed use? Most channels accept delimited text such as CSV or TSV, and XML in either RSS 2.0 or Atom 1.0. Some also offer an API for pushing individual changes between scheduled fetches. The format matters far less than whether the values inside it match the channel's schema. #### Why can I not just export my Shopify products? Because a channel's schema is not Shopify's. Shopify stores a description as HTML where channels want plain text, records availability as inventory quantity and policy where channels want a fixed vocabulary, and has no field at all for a Google product category. Every channel wants a different subset, named differently and formatted differently. #### How is a product feed different from a sitemap? A sitemap lists URLs so a crawler can find your pages. A product feed carries the product data itself, so the channel does not have to infer price, stock or identifiers from a page. The two serve different systems and do not substitute for one another. ### Primary sources - [Google: About product feeds](https://support.google.com/merchants/answer/7439058) --- ## Product identifiers Product identifiers are the values that let a sales channel recognise that your listing and someone else's describe the same physical product: a GTIN, or a manufacturer part number together with a brand. They matter because a channel that can identify a product can attach reviews, specifications and competing offers to it, and one that cannot has to treat your listing as an unknown object. - **Canonical URL:** https://www.feedlab.io/knowledge/concepts/product-identifiers - **Last reviewed:** 16 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Frequently asked questions #### What are unique product identifiers? The values that identify a product independently of who is selling it. In practice that means a GTIN, which is the barcode number assigned by the manufacturer, or the combination of a manufacturer part number and a brand where no GTIN exists. #### Why do channels care about product identifiers? Identification lets the channel attach reviews, specifications and competing offers to the same product, and place your offer among them. Without an identifier your listing is an unknown object that has to be matched on text alone, which is far less reliable. #### What is the difference between GTIN, MPN and SKU? A GTIN is globally unique and identifies the product no matter who sells it. An MPN is assigned by the manufacturer and is only unique within that manufacturer, which is why it needs a brand alongside it. A SKU is a code you invent for your own stock control and means nothing to anyone else. #### What if my products have no identifiers at all? Declare it, by setting identifier_exists to no. That is the correct handling for custom, handmade, vintage and own-brand goods with no manufacturer-assigned identifier. It is not a way to suppress warnings on products that do have barcodes you have not entered. ### Primary sources - [Google: Unique product identifiers](https://support.google.com/merchants/answer/160161) --- ## Variant-level feed A variant-level feed emits one row for each purchasable variant rather than one row per product, because sales channels treat every size, colour and material as a separately buyable item with its own price, stock and barcode. A Shopify product with four sizes in three colours is one product in the admin and twelve items to a channel. - **Canonical URL:** https://www.feedlab.io/knowledge/concepts/variant-level-feed - **Last reviewed:** 17 September 2026 - **Source:** feedlab knowledge base - **Licence:** free to quote and cite with attribution to feedlab ### Frequently asked questions #### Should a product feed have one row per product or per variant? Per variant. Channels treat each variant as a separately purchasable item with its own price, stock level and barcode, so a product with twelve variants produces twelve rows, tied together by a shared item_group_id. #### What breaks in a product-level feed? Shoppers cannot filter by the size or colour they want, because only one variant is described. Price and availability are wrong for every variant except the one submitted. Barcodes, which are per variant, are wrong or missing. And the listing loses the variant picker entirely. #### How do variants stay grouped as one listing? Through item_group_id. Every variant row carries its own unique id plus an item_group_id shared across the product, which tells the channel to present them as one listing with a size or colour selector rather than as unrelated products. #### Does every variant need its own landing page URL? Each row should link to the product page with that variant preselected, usually through the variant query parameter. Without it a shopper lands on the default variant, which may be a different size and price than the listing they clicked. ### Primary sources - [Google: item_group_id attribute specification](https://support.google.com/merchants/answer/6324507) ---