<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Max</title>
    <description>The latest articles on DEV Community by Max (@max_buildogs).</description>
    <link>https://dev.to/max_buildogs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4040371%2F3325063e-c539-470e-b9e4-104fb0b25dcb.png</url>
      <title>DEV Community: Max</title>
      <link>https://dev.to/max_buildogs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/max_buildogs"/>
    <language>en</language>
    <item>
      <title>Shopify structured data warnings your clients can't ignore</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 21 Sep 2026 11:57:07 +0000</pubDate>
      <link>https://dev.to/max_buildogs/shopify-structured-data-warnings-your-clients-cant-ignore-181e</link>
      <guid>https://dev.to/max_buildogs/shopify-structured-data-warnings-your-clients-cant-ignore-181e</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Shopify's &lt;code&gt;structured_data&lt;/code&gt; Liquid filter emits a fixed set of properties (&lt;code&gt;@context&lt;/code&gt;, &lt;code&gt;@id&lt;/code&gt;, &lt;code&gt;@type&lt;/code&gt;, &lt;code&gt;brand&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;offers&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;). Anything outside that list shows as "missing" in Search Console forever, no matter what you change in the theme.&lt;/li&gt;
&lt;li&gt;The same "Missing field" message can mean two different things. Google's Product Snippets result type barely requires anything (&lt;code&gt;name&lt;/code&gt; plus one of &lt;code&gt;review&lt;/code&gt;/&lt;code&gt;aggregateRating&lt;/code&gt;/&lt;code&gt;offers&lt;/code&gt;), while Merchant Listings requires &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, and an &lt;code&gt;Offer&lt;/code&gt; with &lt;code&gt;price&lt;/code&gt; and &lt;code&gt;priceCurrency&lt;/code&gt; above zero. A missing &lt;code&gt;image&lt;/code&gt; is cosmetic for one result type and invalidating for the other.&lt;/li&gt;
&lt;li&gt;If a property the filter does emit (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;offers&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;priceCurrency&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;) shows up as missing on a live product, something on the store is overriding the theme's output: an empty admin field, a hand-rolled JSON-LD snippet, or a second block injected by an app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The bug report you'll get from a client
&lt;/h2&gt;

&lt;p&gt;A client forwards a Search Console screenshot: dozens of "Missing field" warnings under structured data, sitting there for months. Search any Shopify forum or theme vendor help center for this and the advice is always the same: these are warnings, not errors, ignore them.&lt;/p&gt;

&lt;p&gt;That advice is half right, and the half that's wrong is the expensive half. Some of these warnings are the deterministic output of Shopify's own theme code and will never resolve no matter what you touch. Others mean a product is currently invalid for Google Shopping, and nobody noticed because nothing in the Shopify admin surfaces it.&lt;/p&gt;

&lt;p&gt;This is a reference for telling the two cases apart before you spend an afternoon fixing the wrong one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does "invalid" actually mean in this report?
&lt;/h2&gt;

&lt;p&gt;Google's &lt;a href="https://support.google.com/webmasters/answer/7552505" rel="noopener noreferrer"&gt;rich result report docs&lt;/a&gt; don't grade messages by a severity label. They sort by consequence: non-critical issues sit under "Improve item appearance," critical ones under "Why items are invalid." A valid item has no critical issues; an invalid one has at least one, and it won't render as a rich result at all.&lt;/p&gt;

&lt;p&gt;The question to answer for a client isn't "warning or error." It's whether the report marks the item invalid, which Search Console prints right next to the message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does the same "missing image" warning have two different severities?
&lt;/h2&gt;

&lt;p&gt;Google evaluates a product page against more than one result type, and the requirements aren't the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/search/docs/appearance/structured-data/product-snippet" rel="noopener noreferrer"&gt;Product snippets&lt;/a&gt; require &lt;code&gt;name&lt;/code&gt;, plus one of &lt;code&gt;review&lt;/code&gt;, &lt;code&gt;aggregateRating&lt;/code&gt;, or &lt;code&gt;offers&lt;/code&gt;. &lt;code&gt;image&lt;/code&gt; isn't in the required or recommended tables at all.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/search/docs/appearance/structured-data/merchant-listing" rel="noopener noreferrer"&gt;Merchant listings&lt;/a&gt; require &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, and a nested &lt;code&gt;Offer&lt;/code&gt; with &lt;code&gt;price&lt;/code&gt; and &lt;code&gt;priceCurrency&lt;/code&gt;. Google states it directly: "Unlike product snippets, merchant listing experiences require a price greater than zero."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;Missing field "image"&lt;/code&gt; isn't a fixed-severity message; its severity depends on which report you're reading. A merchant &lt;a href="https://community.shopify.com/c/shopify-discussions/missing-field-image-gt-merchant-listing-gt-1-invalid-item/m-p/2337697" rel="noopener noreferrer"&gt;posted the exact failure&lt;/a&gt; to the Shopify community: Merchant Listing report, "1 invalid item detected," missing &lt;code&gt;image&lt;/code&gt;, on nearly every product. The thread ends unanswered.&lt;/p&gt;

&lt;p&gt;If you don't already know which report a given warning came from, check the &lt;a href="https://storecanary.io/blog/merchant-listings-vs-product-snippets-shopify" rel="noopener noreferrer"&gt;merchant listings vs. product snippets&lt;/a&gt; split before doing anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does a stock Shopify theme actually emit?
&lt;/h2&gt;

&lt;p&gt;Everything below hinges on one fact worth verifying yourself. Shopify's &lt;a href="https://shopify.dev/docs/api/liquid/filters/structured_data" rel="noopener noreferrer"&gt;&lt;code&gt;structured_data&lt;/code&gt; Liquid filter&lt;/a&gt;, the line most Dawn-derived themes use to generate product markup, emits a fixed key set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@context, @id, @type, brand, category, description,
image, name, offers, url

offers: @id, @type, availability, price, priceCurrency, url
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same reference documents that a product without variants outputs as &lt;code&gt;Product&lt;/code&gt;, and with one or more variants as &lt;code&gt;ProductGroup&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That list is the whole diagnostic. A property outside it was never going to appear, so its absence tells you nothing. A property inside it that Google reports missing means the live markup diverges from what the filter would have produced. That's a real bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which warnings are safe to close as won't-fix?
&lt;/h2&gt;

&lt;p&gt;These name properties the filter never emits. Expect them on a healthy store, indefinitely:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;Where to actually fix it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Missing field "priceValidUntil"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No price expiry declared; Google decides on its own.&lt;/td&gt;
&lt;td&gt;Theme markup, or leave it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Missing field "shippingDetails"&lt;/code&gt; / &lt;code&gt;"hasMerchantReturnPolicy"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Shipping/return terms aren't in page markup.&lt;/td&gt;
&lt;td&gt;Usually Merchant Center or Search Console settings, not the theme (Google reads settings before markup).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Missing field "aggregateRating"&lt;/code&gt; / &lt;code&gt;"review"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No review data on the page.&lt;/td&gt;
&lt;td&gt;A review app, not the theme.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;No global identifier provided (e.g., gtin, mpn, isbn)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No product identifier declared. Recommended, not required.&lt;/td&gt;
&lt;td&gt;The product record; also affects Merchant Center distribution separately.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of these deserve a second look before you close the ticket. Shipping and returns is the case where editing the theme changes nothing at all: Google documents an order of precedence that puts Merchant Center and Search Console settings above page markup. Identifiers are only "recommended" for rich results while still gating Merchant Center distribution, a separate system reading the same absent value on its own terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which warnings mean you actually have a bug to fix?
&lt;/h2&gt;

&lt;p&gt;These name properties the filter does emit. If Google reports one missing, the live page diverges from stock theme output. On merchant listings, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;offers&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, and &lt;code&gt;priceCurrency&lt;/code&gt; are required, so the item is invalid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Missing field "image"&lt;/code&gt;&lt;/strong&gt;: required for merchant listings. Either no image is assigned in the admin, or the block Google parsed isn't the theme's.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Missing field "name"&lt;/code&gt;&lt;/strong&gt;: required for both result types. The filter always emits it, so its absence means Google parsed a different block than the one the filter produced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Missing field "offers"&lt;/code&gt; / &lt;code&gt;"price"&lt;/code&gt; / &lt;code&gt;"priceCurrency"&lt;/code&gt;&lt;/strong&gt;: required for merchant listings. Check variants first: the filter outputs &lt;code&gt;ProductGroup&lt;/code&gt; when a product has variants, and the offer's position differs from the single-product shape most guides assume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Missing field "description"&lt;/code&gt;&lt;/strong&gt;: emitted by the filter, so its absence points at an empty description field on that specific product. Recommended rather than required, so this costs appearance rather than eligibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The product looks completely normal in the admin: active, priced, photographed, published. The page it actually serves is missing something Google requires.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do "invalid value" warnings mean, as opposed to "missing"?
&lt;/h2&gt;

&lt;p&gt;A third family doesn't say a property is absent. It says the value present can't be used: &lt;code&gt;Invalid ISO 4217 currency code&lt;/code&gt;, &lt;code&gt;Value in field "property name" must be positive&lt;/code&gt;, &lt;code&gt;Duplicate field "property name"&lt;/code&gt;, &lt;code&gt;Invalid enum value in property "property name"&lt;/code&gt;, &lt;code&gt;Invalid URL in field "property name"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These name a value the filter wouldn't produce on its own, since it builds every value from the product record. A malformed value points at a second source on the same page: an app block, a hand-rolled snippet left in the theme, or an old block a migration forgot to remove. &lt;code&gt;Duplicate field&lt;/code&gt; in particular is the tell that two JSON-LD blocks are competing on one page, and Google is reading the wrong one (or both).&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you actually check a client's store?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Note which report the warning is in.&lt;/strong&gt; Merchant Listings or Product Snippets, and whether the item is marked invalid or only flagged for appearance. Everything else follows from these two facts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the markup Google actually receives&lt;/strong&gt;, not your theme source. Run the URL through the &lt;a href="https://search.google.com/test/rich-results" rel="noopener noreferrer"&gt;Rich Results Test&lt;/a&gt; and count the &lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;&lt;/code&gt; blocks on the page. More than one product block is a common reason the parsed markup differs from what the theme alone would output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare the reported property against the filter's key list above.&lt;/strong&gt; Outside the list, it's structural and won't change. Inside it, something on the store removed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix structural ones at their real source&lt;/strong&gt;, not in the theme: shipping/returns through Merchant Center or Search Console settings, identifiers on the product record, reviews through the review app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat a missing required property as a broken page, not a warning.&lt;/strong&gt; Find the block that dropped &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;offers&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, or &lt;code&gt;priceCurrency&lt;/code&gt;, and fix that block instead of layering a second one on top.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One caveat: if you check by viewing page source instead of the Rich Results Test, remember JS-injected markup can be present in Google's rendered view and absent from raw source (or the reverse). Raw source isn't proof either way, the same trap behind phantom &lt;code&gt;noindex&lt;/code&gt; tags that only appear in the rendered DOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does the same warning keep reappearing after you fix it?
&lt;/h2&gt;

&lt;p&gt;The won't-fix group is stable. Once you know which properties the theme never emits, those warnings stop being informative.&lt;/p&gt;

&lt;p&gt;The actual-bug group isn't stable, which is the argument for monitoring instead of a one-time audit. A review app adds a block. A theme update replaces the file holding a hand-rolled snippet. A merchandiser removes a product's last variant, changing whether the page declares &lt;code&gt;Product&lt;/code&gt; or &lt;code&gt;ProductGroup&lt;/code&gt;. Someone deletes a photo during routine catalog cleanup, and merchant listings just lost a required property. None of this looks like a risky change to whoever made it; it's routine store operation, and each change can silently alter what the page declares to Google.&lt;/p&gt;

&lt;p&gt;Nothing announces it when this happens. Search Console reports on its own crawl schedule, so today's report describes pages Google fetched some time ago. The Shopify admin has no screen at all for the shape of a page's structured data. The product looks fine everywhere you'd think to check, which is why a client can be told "warnings are optional" in one audit and be sitting on invalid merchant listings by the next.&lt;/p&gt;

&lt;p&gt;If you build or maintain Shopify stores for clients, how do you currently catch this? Manual Rich Results Test spot-checks, a recurring Search Console export, something custom? Curious what's actually working for other agencies.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article, with screenshots and ongoing updates, lives on &lt;a href="https://storecanary.io/blog/shopify-structured-data-warnings" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>googlesearch</category>
    </item>
    <item>
      <title>The Google Merchant Center setting Shopify won't show you</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 16 Sep 2026 11:57:07 +0000</pubDate>
      <link>https://dev.to/max_buildogs/the-google-merchant-center-setting-shopify-wont-show-you-4gch</link>
      <guid>https://dev.to/max_buildogs/the-google-merchant-center-setting-shopify-wont-show-you-4gch</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Approved" in Merchant Center only means a product passed review for &lt;em&gt;some&lt;/em&gt; marketing method. It says nothing about whether free listings specifically is turned on.&lt;/li&gt;
&lt;li&gt;The opt-in lives on the Merchant Center data source, under Settings → Data sources → [source] → Data source setup → Marketing methods. Shopify admin has no equivalent screen.&lt;/li&gt;
&lt;li&gt;Stores whose Merchant Center account was created or relinked outside the Shopify app (agency-managed accounts, pre-existing accounts, rebuilds after a suspension) often never got the automatic free-listings opt-in and need it confirmed by hand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build or maintain Shopify stores for clients, you've probably hit this one: every product in Merchant Center reads "Approved," nothing is disapproved, the feed is syncing cleanly, and the client wants to know why none of their products show up on Google's Shopping tab unless they're paying for ads. It looks like a data-quality bug, so you check GTINs, availability and price sync, and everything checks out. That's because the actual cause isn't in the product data. It's a marketing-method opt-in that lives entirely on the Merchant Center side, has no equivalent setting in Shopify admin, and defaults to off for a specific, documented set of account setups. Here's what "Approved" actually guarantees, where the opt-in lives, and the four causes worth checking on a client account before debugging product feeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does "Approved" in Merchant Center mean a product shows in free listings?
&lt;/h2&gt;

&lt;p&gt;Google's definition, from its &lt;a href="https://support.google.com/merchants/answer/12488713" rel="noopener noreferrer"&gt;product status documentation&lt;/a&gt;, is narrower than it looks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Approved: Google has completed reviewing the product, and the product is now showing on Google."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"Showing on Google" is the entire claim, not "showing on every surface Google offers." The status that actually names the problem is easy to skim past:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Limited: The product is showing on Google, but only in some instances... This may mean that the product is eligible to show in some countries but not others, or may not be showing for some marketing methods."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Google tells you eligibility is per-surface, but it doesn't tell you which surface you lost. For that you have to check the data source directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the actual difference between free listings and Shopping ads?
&lt;/h2&gt;

&lt;p&gt;Google's &lt;a href="https://support.google.com/merchants/answer/15130232" rel="noopener noreferrer"&gt;Marketing methods documentation&lt;/a&gt; defines them plainly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Marketing methods (previously known as Destinations) refer to how and where your products are shown on Google to potential customers."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For an online store, the list includes free listings, Shopping ads, YouTube store, YouTube affiliate and dynamic remarketing. They're siblings, not a default-plus-upgrade pair. Free listings even has its own &lt;a href="https://support.google.com/merchants/answer/12073010" rel="noopener noreferrer"&gt;policy document&lt;/a&gt;, separate from Shopping ads policies, and enforcement on one doesn't necessarily touch the other. That's the mechanism that lets a client's paid ads keep running while the free listings for the exact same products go dark, with nothing in the dashboard flagging the split.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you check whether a client's data source is opted into free listings?
&lt;/h2&gt;

&lt;p&gt;Four checks, cheapest first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Search the Shopping tab&lt;/strong&gt; in a private window for the exact product title plus the store name. If a paid listing shows up and no free one does, you've confirmed the two marketing methods are behaving differently for the same product, which rules out most catalogue-wide data issues immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open the product in Merchant Center and read its actual status&lt;/strong&gt;, not the dashboard's green count. Approved, Limited and Not approved are three different answers, and Limited is the one meaning "somewhere, not everywhere."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the Marketing methods field on the data source itself.&lt;/strong&gt; Settings → Data sources → the product source the store sends → Data source setup tab → Marketing methods. This is the same field Google's own instructions use for &lt;a href="https://support.google.com/merchants/answer/13692890" rel="noopener noreferrer"&gt;opting a source out of free listings&lt;/a&gt;, so it's also where you confirm it's opted in. If free listings isn't listed there, nothing about the individual products will change the outcome.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter Needs attention by marketing method&lt;/strong&gt; instead of reading the combined count. A catalogue can look fine in aggregate while one specific marketing method sits at zero.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every one of those checks happens inside Merchant Center, not Shopify. Shopify's own docs confirm &lt;a href="https://help.shopify.com/en/manual/online-sales-channels/marketplaces/google/shopping-on-google/google-listings" rel="noopener noreferrer"&gt;the Google &amp;amp; YouTube channel is managed by Google&lt;/a&gt;, and there's no admin screen that surfaces marketing-method status because the setting doesn't live in Shopify at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What causes the free-listings opt-in to be off in the first place?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The data source was never opted in.&lt;/strong&gt; Google's own page on this, written specifically for the &lt;a href="https://support.google.com/merchants/answer/13692890" rel="noopener noreferrer"&gt;Google &amp;amp; YouTube Shopify app&lt;/a&gt;, splits behavior by how the account was created: "If you created your Merchant Center account by using the Google &amp;amp; YouTube app on Shopify... your approved products can automatically start appearing on the Shopping tab." But: "If you created your Merchant Center account outside of Shopify, then you need to use Merchant Center to confirm whether your products can appear." Any client who was already running Shopping ads before moving to Shopify, or whose agency linked an existing account, or who rebuilt after a suspension, lands in the branch where nothing is automatic. The connect flow doesn't say so, and products still sync and get approved either way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The store or country is outside the eligibility set.&lt;/strong&gt; The same page lists supported countries for free listings. A ten-second check, worth ruling out before the harder ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The account-level requirements were never met.&lt;/strong&gt; &lt;a href="https://support.google.com/merchants/answer/13889434" rel="noopener noreferrer"&gt;Free listings requirements&lt;/a&gt; call for return-policy information on the store's website, and either Shipping settings or a &lt;code&gt;shipping&lt;/code&gt; attribute on the product data, required in a list of countries including the US, UK, Canada, Australia, Germany and France. These are Merchant Center and website-level requirements, separate from the &lt;code&gt;shippingDetails&lt;/code&gt; and &lt;code&gt;hasMerchantReturnPolicy&lt;/code&gt; structured-data properties Google reads from the product page itself. It's possible to have the page markup correct and the Merchant Center settings blank, or the other way around; nothing reconciles the two automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. A product-level exclusion is hiding specific items.&lt;/strong&gt; Google documents &lt;code&gt;excluded_destination&lt;/code&gt; for hiding a marketing method on individual products, and &lt;code&gt;included_destination&lt;/code&gt; for re-including items that would otherwise be excluded by a default. An exclusion set by an old feed app, an import template, or a previous agency affects exactly the products it names and leaves the rest of the catalogue behaving normally, which is why it survives the "but most of my products are fine" test that rules out the other three causes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do free listings require free shipping?
&lt;/h2&gt;

&lt;p&gt;No, and this is the wrong answer that circulates most often in support threads. The requirement is that shipping costs be declared, through Shipping settings or the &lt;code&gt;shipping&lt;/code&gt; attribute. Declaring a $12 flat rate satisfies it; declaring nothing doesn't. A &lt;a href="https://community.shopify.com/t/google-merchant-center-approved-but-no-product-visibility-in-google-shopping/393738" rel="noopener noreferrer"&gt;February 2025 Shopify Community thread&lt;/a&gt; on exactly this symptom gets replies about incognito windows and organic SEO, but nobody checks which marketing methods the data source is opted into. It ends unresolved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this keep coming back on client accounts?
&lt;/h2&gt;

&lt;p&gt;A setting you can't see from the tool you work in daily is a setting you can't monitor. The opt-in lives on the data source, and data sources get replaced: a new feed app, an agency rebuilding the connection, a Comparison Shopping Service change, a re-link after an account issue. None of those events announces what it did to the marketing-method opt-in, and Shopify's own &lt;a href="https://help.shopify.com/en/manual/online-sales-channels/marketplaces/google/getting-setup/product-disapprovals-warnings" rel="noopener noreferrer"&gt;product-status documentation&lt;/a&gt; lumps both surfaces into one sentence: invalid or missing data means products "don't display in free listings and Google Shopping ads," with no way to tell which one actually failed.&lt;/p&gt;

&lt;p&gt;Paid ads are what makes this invisible in practice. If a client is running Shopping ads, traffic keeps arriving while the free surface is dark, and the loss shows up as an unexplained inefficiency rather than an obvious outage. This isn't something a storefront or page-markup audit will catch either, since the opt-in is an account-level Merchant Center setting with no footprint on the page itself. The only reliable check is opening the data source and reading the Marketing methods field directly, on every client account, not just the ones that complain first.&lt;/p&gt;

&lt;p&gt;Do you check this on client handoffs, or only after a client flags it?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article — with screenshots and ongoing updates — lives on &lt;a href="https://storecanary.io/blog/shopify-products-not-in-google-free-listings" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>googlesearch</category>
    </item>
    <item>
      <title>The Shopify variant bug that breaks Google Shopping listings</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 14 Sep 2026 11:57:02 +0000</pubDate>
      <link>https://dev.to/max_buildogs/the-shopify-variant-bug-that-breaks-google-shopping-listings-5eod</link>
      <guid>https://dev.to/max_buildogs/the-shopify-variant-bug-that-breaks-google-shopping-listings-5eod</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search Console tracks two separate rich-result reports for products: Product snippets (cosmetic enhancements on a normal search result) and Merchant listings (eligibility for shopping carousels and knowledge panels). Merchant listings require a real &lt;code&gt;Offer&lt;/code&gt; with a price above zero; an &lt;code&gt;AggregateOffer&lt;/code&gt; is not enough.&lt;/li&gt;
&lt;li&gt;On Shopify, any product with one or more variants renders as a &lt;code&gt;ProductGroup&lt;/code&gt;, not a &lt;code&gt;Product&lt;/code&gt;. The price sits inside each entry of &lt;code&gt;hasVariant&lt;/code&gt;, one per variant, never at the top level. A check that only reads the top-level entity will report a missing price on a page that is marked up correctly.&lt;/li&gt;
&lt;li&gt;Apps that inject their own &lt;code&gt;Product&lt;/code&gt; block (reviews, rich snippets) next to the theme's &lt;code&gt;ProductGroup&lt;/code&gt; describe the same item twice and can knock a product out of eligibility even when the theme markup is fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build or maintain Shopify storefronts for clients, you've probably seen "Merchant listings" and "Product snippets" sitting side by side in a client's Search Console account with no explanation of why a product is in one and not the other. It's a structured data mismatch that's easy to introduce during a completely ordinary change: a theme update, a reviews app install, a custom product template. The two reports look similar enough that most audits treat them as interchangeable, but eligibility for merchant listings depends on where the &lt;code&gt;Offer&lt;/code&gt; object actually lives in the markup, and on Shopify that's not always where you'd expect. This piece covers what separates the two reports, where Shopify puts the price on variant products (and why that trips up naive checks), and a five-step process for diagnosing which report a client's catalog should be in before changing any code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the actual difference between the two reports?
&lt;/h2&gt;

&lt;p&gt;Google's &lt;a href="https://support.google.com/webmasters/answer/12660034" rel="noopener noreferrer"&gt;Shopping reports and tools&lt;/a&gt; documentation defines both in a single line each. Product snippets is "a rich result report for Product snippet structured data found on your site," covering results shown "with visual enhancements, for example, product ratings and prices." Merchant listings is "a rich result report for Merchant listing structured data found on your site," and those listings "enable more full-featured search results and always include a price" and "might appear in a carousel of similar products from different sellers, or in a knowledge panel in search results."&lt;/p&gt;

&lt;p&gt;A product snippet decorates the blue link a page already has. A merchant listing moves a page into a different class of result: the carousels and knowledge panels where shoppers compare sellers directly. For an ecommerce client, that second surface is the commercially relevant one, and it has the stricter entry condition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does eligibility hinge on the Offer object?
&lt;/h2&gt;

&lt;p&gt;Google's &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/merchant-listing" rel="noopener noreferrer"&gt;merchant listing documentation&lt;/a&gt; states the distinction directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Product snippets accept an &lt;code&gt;Offer&lt;/code&gt; or &lt;code&gt;AggregateOffer&lt;/code&gt; but merchant listings require an &lt;code&gt;Offer&lt;/code&gt; as the merchant has to be the seller of the product."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And a threshold worth checking for explicitly: "Unlike product snippets, merchant listing experiences require a price greater than zero."&lt;/p&gt;

&lt;p&gt;Read together: an &lt;code&gt;AggregateOffer&lt;/code&gt; (a price range, the shape a comparison page would use) is enough for a product snippet and not enough for a merchant listing. A price of zero doesn't just look wrong, it puts the item below the floor for the report that unlocks shopping surfaces. Everything else inside that &lt;code&gt;Offer&lt;/code&gt; (&lt;code&gt;priceValidUntil&lt;/code&gt;, identifiers, etc.) follows the same pattern: recommended rather than required, so a missing field produces a warning instead of an outright exclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does Shopify actually put the price on a variant product?
&lt;/h2&gt;

&lt;p&gt;This part is specific to Shopify rather than to structured data generally, and it's documented by Shopify itself. The &lt;a href="https://shopify.dev/docs/api/liquid/filters/structured_data" rel="noopener noreferrer"&gt;&lt;code&gt;structured_data&lt;/code&gt; Liquid filter&lt;/a&gt;, the one &lt;a href="https://github.com/Shopify/dawn/blob/main/sections/main-product.liquid" rel="noopener noreferrer"&gt;Dawn calls in &lt;code&gt;sections/main-product.liquid&lt;/code&gt;&lt;/a&gt; as a single &lt;code&gt;{{ product | structured_data }}&lt;/code&gt; line, describes its own output like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Product objects are output as a schema.org &lt;code&gt;Product&lt;/code&gt; if they have no variants, and a &lt;code&gt;ProductGroup&lt;/code&gt; if they have one or more variants."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On a product with variants, the top-level entity on the page isn't a &lt;code&gt;Product&lt;/code&gt;. It's a &lt;code&gt;ProductGroup&lt;/code&gt;, and the offers live one level down, inside &lt;code&gt;hasVariant&lt;/code&gt;, one offer per variant, each with its own price and its own &lt;code&gt;?variant=&lt;/code&gt; URL. Nothing sits at the top level declaring a single price, because with several variants there isn't one.&lt;/p&gt;

&lt;p&gt;I checked four live Shopify storefronts, fetching each product page as Googlebot and parsing every &lt;code&gt;application/ld+json&lt;/code&gt; block in the served HTML. Two emitted a &lt;code&gt;ProductGroup&lt;/code&gt; with no top-level &lt;code&gt;offers&lt;/code&gt; and the price inside each variant, matching the filter's documented behavior. The other two emitted no product structured data at all in the served HTML; one returned only a &lt;code&gt;CollectionPage&lt;/code&gt; and an &lt;code&gt;FAQPage&lt;/code&gt; on a product URL. None of the four emitted a plain &lt;code&gt;Product&lt;/code&gt; with a price at the top, which is the shape most third-party audit scripts assume when they grep a page for &lt;code&gt;"price"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The practical consequence: a check that only reads the top-level entity will report a page as missing price, availability, and identifiers when the page is marked up correctly. Worth confirming before acting on that kind of report.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do variant relationships affect this?
&lt;/h2&gt;

&lt;p&gt;Google treats variant relationships as something declared deliberately. Its &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/product-variants" rel="noopener noreferrer"&gt;product variant documentation&lt;/a&gt; asks for the &lt;code&gt;ProductGroup&lt;/code&gt; class with &lt;code&gt;variesBy&lt;/code&gt;, &lt;code&gt;hasVariant&lt;/code&gt;, and &lt;code&gt;productGroupID&lt;/code&gt; (described as "the identifier of the product group, also known as the parent sku"). It also sets a structural precondition Shopify satisfies by default: "The site must have the ability to preselect each variant directly with a distinct URL (using URL query parameters)."&lt;/p&gt;

&lt;p&gt;It connects this directly to the topic at hand: "Adding this markup also makes your products eligible for display with variant information in merchant listing experiences." Variant markup feeds directly into merchant listing eligibility rather than sitting beside it as a separate concern.&lt;/p&gt;

&lt;p&gt;In the stores checked, a product whose only variant was the default one still emitted a &lt;code&gt;ProductGroup&lt;/code&gt;, just with no &lt;code&gt;variesBy&lt;/code&gt; property: a group that varies by nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you read the combination of reports a client has?
&lt;/h2&gt;

&lt;p&gt;The most useful diagnostic in Search Console isn't either report alone, it's which of three related reports exist at all. Google documents a third one, Merchant opportunities, which "shows recommendations for improving how your online shop appears on Google" and appears "if you have an eligible site that sells physical goods."&lt;/p&gt;

&lt;p&gt;If Merchant opportunities is present and the two rich-result reports aren't, Google states the cause is that either "Google hasn't found structured data on your site, or Google found structured data, but the sample data used by Search Console to generate reports doesn't contain any entity data." That second clause matters: the reports are built from a sample, so an absent report isn't proof that no page on the site carries the markup.&lt;/p&gt;

&lt;p&gt;The reverse combination has its own meaning too. If the product reports exist and Merchant opportunities doesn't, Google "found structured data on your site, but doesn't recognize your site as an online shop that sells physical goods," a statement about how the whole site is classified, not about any single product page.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you audit a client's store in five steps?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read which reports exist at all.&lt;/strong&gt; Open the Shopping section of Search Console and note which of Merchant listings, Product snippets, and Merchant opportunities are present. The combination is the diagnosis; an absent report carries as much signal as a present one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View source on a product with variants.&lt;/strong&gt; Read the JSON-LD in the served HTML, not in a rendered DOM inspector. Check whether the top-level entity says &lt;code&gt;Product&lt;/code&gt; or &lt;code&gt;ProductGroup&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find where the offer actually sits.&lt;/strong&gt; If it's a &lt;code&gt;ProductGroup&lt;/code&gt;, the offers are inside &lt;code&gt;hasVariant&lt;/code&gt;, one per variant. Confirm each carries &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;priceCurrency&lt;/code&gt;, and &lt;code&gt;availability&lt;/code&gt;, and that no price is zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count the product entities on the page.&lt;/strong&gt; List the top-level type of every &lt;code&gt;application/ld+json&lt;/code&gt; block. A page declaring a &lt;code&gt;ProductGroup&lt;/code&gt; from the theme and a separate &lt;code&gt;Product&lt;/code&gt; from an app is describing the same item twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare the report against catalog size.&lt;/strong&gt; Put the number of valid items in the Merchant listings report next to the number of products actually sold. A large gap points at a subset of products whose markup differs from the rest, which is more tractable than a store-wide problem.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What actually causes this, and how do you fix each cause?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No product markup in the served HTML.&lt;/strong&gt; Check this first; it's total rather than partial. If a product URL returns no &lt;code&gt;Product&lt;/code&gt; or &lt;code&gt;ProductGroup&lt;/code&gt; at all, nothing else matters until that's fixed. Usually the theme's markup was removed during customization, or the page is assembled client-side. Google's reports are built from what the crawler receives, so markup that only exists after JavaScript runs is a real risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An offer that can't clear the merchant-listing bar.&lt;/strong&gt; A missing price, a price of zero, or an &lt;code&gt;AggregateOffer&lt;/code&gt; where an &lt;code&gt;Offer&lt;/code&gt; is required all keep an item out of the merchant listing report while leaving it eligible as a product snippet. That asymmetry is why a store can look fine in one report and be absent from the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two entities describing one product.&lt;/strong&gt; When a review app, a rich-snippet app, or a theme section adds its own &lt;code&gt;Product&lt;/code&gt; block beside the theme's &lt;code&gt;ProductGroup&lt;/code&gt;, the page makes two claims about one item. Find the second block before editing the first; removing an app's output is usually easier than making the theme agree with it. If the two blocks disagree about price or availability, the consequences reach past these reports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Markup that's present but doesn't parse.&lt;/strong&gt; A single stray character voids an entire JSON-LD block, and Search Console reports that separately, under its own label ("Unparsable structured data"). If a client's reports are empty and you expected otherwise, rule this out before assuming an eligibility problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this keep resurfacing on client sites?
&lt;/h2&gt;

&lt;p&gt;None of the changes that trigger this look like changes to Google. Installing a reviews app, letting a theme update land, retiring the last variant of a product, or having a different developer customize a product template are all ordinary operations, and each can move a page across the line between the two reports. Nothing in Shopify admin surfaces the shape of a store's structured data, and Search Console shows the consequence weeks later, in a report with no alert attached. Making the fix once doesn't close the loop: the same routine decisions will trip this again on the next theme update or app install, and nothing in the stack flags it automatically.&lt;/p&gt;

&lt;p&gt;Have you run into this on a client's store, a &lt;code&gt;ProductGroup&lt;/code&gt; with no top-level offer, or two conflicting &lt;code&gt;Product&lt;/code&gt; blocks on the same page? What caught it: a manual audit, or something automated?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article, with screenshots and ongoing updates, lives on &lt;a href="https://storecanary.io/blog/merchant-listings-vs-product-snippets-shopify" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>googlesearch</category>
    </item>
    <item>
      <title>Debugging the Shopify 403 error that blocks Googlebot</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 09 Sep 2026 11:56:20 +0000</pubDate>
      <link>https://dev.to/max_buildogs/debugging-the-shopify-403-error-that-blocks-googlebot-5dcg</link>
      <guid>https://dev.to/max_buildogs/debugging-the-shopify-403-error-that-blocks-googlebot-5dcg</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;"Blocked due to access forbidden (403)" in Search Console means something answered Googlebot with a 403 instead of the page. Google documents this as a misconfiguration, since Googlebot never sends credentials in the first place.&lt;/li&gt;
&lt;li&gt;On Shopify, the merchant (and you, as the developer) don't run the server, so the 403 is coming from something sitting between Google and Shopify: an unsupported proxy, a geo-restriction rule, or a third-party app answering an app-proxy path.&lt;/li&gt;
&lt;li&gt;The fix depends entirely on which of those it is. Sorting the affected URLs and running a live test in Search Console is the first step, not editing a firewall that doesn't exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build or maintain Shopify storefronts for clients, sooner or later you'll open Search Console and find a row you can't immediately explain: "Blocked due to access forbidden (403)" under Page indexing. Google's definition is unusually blunt about it: the response itself is wrong, not just restrictive. The problem is that the standard playbook for a 403 (check &lt;code&gt;.htaccess&lt;/code&gt;, check firewall rules, check server logs) assumes you run the server. On Shopify you don't. The client's storefront runs on infrastructure neither of you can touch, so a 403 has to originate from something between Googlebot and Shopify: a proxy, a geo-restriction app, a third-party app's backend answering an app-proxy path, or a domain that isn't actually pointed at Shopify anymore. Here's how to figure out which one, and fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does "blocked due to access forbidden (403)" actually mean?
&lt;/h2&gt;

&lt;p&gt;Google's &lt;a href="https://support.google.com/webmasters/answer/7440203" rel="noopener noreferrer"&gt;Page indexing report documentation&lt;/a&gt; is more opinionated than most status descriptions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"HTTP 403 means that the user agent provided credentials, but was not granted access. However, Googlebot never provides credentials, so your server is returning this error incorrectly. The page will not be indexed."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two things follow. First, Google isn't describing a legitimately protected page; it's saying the response is wrong for the request that was made. A 403 is what you send a client that authenticated and still lacks permission. Googlebot never authenticates, so it should get a 200 or a 404, not a 403.&lt;/p&gt;

&lt;p&gt;Second, this is a different failure from its neighbors in the same report. &lt;strong&gt;Blocked by robots.txt&lt;/strong&gt; is a rule Googlebot reads and voluntarily obeys; it never makes the request. &lt;strong&gt;Blocked due to unauthorized request (401)&lt;/strong&gt; is a server asking for credentials. &lt;strong&gt;403&lt;/strong&gt; is a request that was actually made, received, and actively refused. Something processed the connection and decided against it, which narrows the suspects considerably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why doesn't the standard 403 advice apply on Shopify?
&lt;/h2&gt;

&lt;p&gt;Google's own remedy on the Shopping side makes the mismatch obvious. Merchant Center's &lt;a href="https://support.google.com/merchants/answer/14990960" rel="noopener noreferrer"&gt;product crawl issues page&lt;/a&gt; lists the causes as being "behind a firewall, geo-restrictions, or a private IP that blocks our crawlers," and instructs you to "ask your webmaster or hosting provider to update your firewall and server settings."&lt;/p&gt;

&lt;p&gt;On Shopify, your client is nominally the webmaster and Shopify is the host, but Shopify doesn't expose firewall or server settings to change. Checking a handful of platform endpoints on two live Shopify storefronts with a Googlebot user agent returned &lt;code&gt;200&lt;/code&gt; on &lt;code&gt;/cart.js&lt;/code&gt;, &lt;code&gt;302&lt;/code&gt; on &lt;code&gt;/account&lt;/code&gt;, and &lt;code&gt;404&lt;/code&gt;/&lt;code&gt;405&lt;/code&gt; on Shopify's internal telemetry path, never &lt;code&gt;403&lt;/code&gt;. That's a small sample, not a guarantee about the platform, but it's enough to justify the working assumption: &lt;strong&gt;the 403 is coming from something other than Shopify itself, and the job is to find out what.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Which of the affected URLs actually need fixing?
&lt;/h2&gt;

&lt;p&gt;Before diagnosing anything, pull the URL list attached to the status. It mixes together things that mean very different amounts of urgency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product, collection or page URLs the client sells from.&lt;/strong&gt; This is the emergency; Google says plainly it won't index these.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App proxy paths&lt;/strong&gt;, anything under &lt;code&gt;/apps/&lt;/code&gt;, &lt;code&gt;/a/&lt;/code&gt;, &lt;code&gt;/community/&lt;/code&gt; or &lt;code&gt;/tools/&lt;/code&gt;. Not served by Shopify at all (see below), usually not urgent, and usually not fixable from the storefront's side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paths neither of you recognize.&lt;/strong&gt; Stores accumulate URLs from old apps, retired themes and dead integrations. Worth understanding, rarely worth an incident.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Forty 403s on paths nobody buys from is cleanup. One 403 on the client's best-selling product is a same-day fix. Sort the list before deciding how urgent this is.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you confirm the block is still happening?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Export the URLs from the 403 row&lt;/strong&gt; and split them into the three groups above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a live test on one affected product URL.&lt;/strong&gt; In Search Console's &lt;a href="https://support.google.com/webmasters/answer/9012289" rel="noopener noreferrer"&gt;URL Inspection tool&lt;/a&gt;, use &lt;strong&gt;Test live URL&lt;/strong&gt;. The report shows what happened at the last crawl; the live test shows whether it's still happening now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check where the domain actually resolves.&lt;/strong&gt; Shopify's &lt;a href="https://help.shopify.com/en/manual/domains/add-a-domain/connecting-domains/connect-domain-manual" rel="noopener noreferrer"&gt;manual domain connection guide&lt;/a&gt; documents an &lt;code&gt;A&lt;/code&gt; record pointing to &lt;code&gt;23.227.38.65&lt;/code&gt; and a &lt;code&gt;www&lt;/code&gt; &lt;code&gt;CNAME&lt;/code&gt; pointing to &lt;code&gt;shops.myshopify.com&lt;/code&gt;. Anything else means something other than Shopify is answering on that domain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load the store yourself from a normal connection.&lt;/strong&gt; If it loads fine for you but Google is refused, you're looking at something that treats automated clients differently from browsers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check Merchant Center separately.&lt;/strong&gt; The same &lt;a href="https://support.google.com/merchants/answer/14990960" rel="noopener noreferrer"&gt;crawl issues page&lt;/a&gt; notes "Google uses AdsBot-Google for ads and Googlebot for organic search." Two crawlers, two consoles: a clean Search Console doesn't mean Merchant Center is clean.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What can actually return a 403 to Googlebot on a Shopify domain?
&lt;/h2&gt;

&lt;p&gt;Four mechanisms are documented well enough to name, ordered by how fast you can rule each one out.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. A proxy in front of the domain
&lt;/h3&gt;

&lt;p&gt;Shopify's &lt;a href="https://help.shopify.com/en/manual/domains/troubleshoot-issues-with-domains" rel="noopener noreferrer"&gt;domain troubleshooting page&lt;/a&gt; states plainly that "Shopify doesn't support the setup of services such as Cloudflare DNS and Orange-to-Orange (O2O)" and that such a setup "could break at any time because of changes on either the Cloudflare or Shopify side." One listed problem is "reduced bot detection accuracy," because request attributes get altered before they reach Shopify. A proxy in this position can classify a crawler as unwanted traffic and answer with a 403, and it does so before Shopify ever sees the request, so nothing in Shopify admin will show it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Region restrictions that exclude where Google crawls from
&lt;/h3&gt;

&lt;p&gt;Merchant Center is explicit: "If you restrict specific regions, you must allow crawling from the US because Google often operates from there." A country-blocking app, a fraud-prevention rule, or a proxy geo-filter can refuse Google without anyone intending to touch a crawler. The store works perfectly for every real customer, which is exactly why the problem goes unnoticed.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. An app proxy path answered by someone else's server
&lt;/h3&gt;

&lt;p&gt;Shopify's &lt;a href="https://shopify.dev/docs/apps/build/online-store/display-dynamic-data" rel="noopener noreferrer"&gt;app proxy documentation&lt;/a&gt; explains that app proxies "take requests to Shopify URLs, and proxy them to your app." A request under &lt;code&gt;apps&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;community&lt;/code&gt; or &lt;code&gt;tools&lt;/code&gt; on the storefront domain gets forwarded to the app developer's own server, which produces the response. A 403 there is the client's domain in Search Console, but it's neither the client's server nor Shopify's; you can't fix it from Shopify admin, and the only useful move is reporting the exact URL to the app vendor.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. A domain that isn't actually pointed at Shopify
&lt;/h3&gt;

&lt;p&gt;If a domain is parked or redirected through a third-party host instead of connected directly to Shopify, that host answers Googlebot, and its defaults decide what Googlebot receives. A merchant described exactly this shape in a &lt;a href="https://community.shopify.com/t/google-page-indexing-blocked-due-to-access-forbidden-403/285682" rel="noopener noreferrer"&gt;Shopify Community thread&lt;/a&gt;: a domain "redirected to my shopify site" while Search Console showed the 403 status. Step 3 above rules this in or out.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you fix each cause?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Proxy in front of the domain:&lt;/strong&gt; remove it so requests reach Shopify directly. Shopify calls this setup "outside the scope of Shopify Support" and points to the third-party domain host for proxy settings, so expect the fix to happen at the DNS provider, not in Shopify admin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Region restrictions:&lt;/strong&gt; allow the regions Google crawls from, US included, in whatever app or rule enforces the restriction. If the tool filters by user agent instead of geography, allow Googlebot and AdsBot-Google explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App proxy path:&lt;/strong&gt; report the URL to the app vendor. If the path serves nothing a shopper needs, removing the app or feature is the alternative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain not pointed at Shopify:&lt;/strong&gt; reconnect it using the DNS records Shopify documents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One caution on allowlisting by user agent: anything can claim to be Googlebot. Google publishes its &lt;a href="https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot" rel="noopener noreferrer"&gt;crawler IP ranges as JSON&lt;/a&gt;, and matching against those ranges is the accurate way to implement "let Google through" if the blocking layer supports IP-based rules.&lt;/p&gt;

&lt;p&gt;After the fix, re-run the live test on the same URL. The report keeps showing the old status until Google recrawls, so the live test is how you confirm the fix worked before the console catches up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this keep coming back on client stores?
&lt;/h2&gt;

&lt;p&gt;Every mechanism above lives outside Shopify admin, and three of the four are owned by somebody else. A proxy configuration changes when a DNS provider updates a default. A geo-restriction rule gets tightened after a fraud incident. An app vendor redeploys and their server starts refusing crawlers on a path mounted on the client's domain.&lt;/p&gt;

&lt;p&gt;None of those events trigger a notification. Shopify admin shows the product as active, priced and published the whole time, because from Shopify's point of view nothing is wrong; the request never reached it. Search Console reports it eventually, on its own crawl schedule. For an agency managing several client stores, that gap between the refusal starting and someone actually finding it is worth closing with a recurring check rather than waiting for a client to notice a sales drop.&lt;/p&gt;

&lt;p&gt;Have you run into a 403 that turned out to be a third-party app or proxy on a client's store? What was it?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article, with screenshots and ongoing updates, lives on &lt;a href="https://storecanary.io/blog/blocked-due-to-access-forbidden-403-shopify" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>googlesearch</category>
    </item>
    <item>
      <title>Shopify products can disappear from Google with zero alerts</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 07 Sep 2026 11:57:04 +0000</pubDate>
      <link>https://dev.to/max_buildogs/shopify-products-can-disappear-from-google-with-zero-alerts-1ai1</link>
      <guid>https://dev.to/max_buildogs/shopify-products-can-disappear-from-google-with-zero-alerts-1ai1</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Google documents a warning email only for Merchant Center &lt;em&gt;account-level&lt;/em&gt; suspensions. A single disapproved product, or anything on the organic Search side, has no documented email at all.&lt;/li&gt;
&lt;li&gt;Search Console and the URL Inspection tool only show the state from Google's last crawl, not the live page, so a change can sit unreported for weeks on a low-traffic client store.&lt;/li&gt;
&lt;li&gt;A generic uptime or content-diff monitor won't catch any of this: noindex tags, canonical swaps, feed mismatches, and sitemap removals all leave the page responding normally while it quietly drops out of Search.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A client asks why a product stopped showing up in Google, and the honest answer is often that nothing on your end technically failed. The page returns 200, the theme renders fine, and Shopify admin shows the product as active. Somewhere between your build and Google's index though, a noindex tag survived a theme migration, an app rewrote the canonical URL, a feed price drifted out of sync with the live page, or the product quietly fell out of the sitemap. None of these throw an error. None of them show up in a deploy log, and for most of them, Google never emails anyone either.&lt;/p&gt;

&lt;p&gt;This matters more for the people building these stores than for the merchant, because you're usually the one asked to explain a traffic drop weeks after it happened. Using Google's own documentation, here's which of these failures actually generate a notification, which only surface if someone opens the right report, and which produce no signal anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Merchant Center actually email you about a disapproved product?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.google.com/merchants/answer/13693195?hl=en" rel="noopener noreferrer"&gt;Google's page on Merchant Center warnings and account suspensions&lt;/a&gt; says you'll get a warning email with details of the issue, how to fix it, and a timeframe to act, and that products may keep showing in a limited state until that period ends. &lt;a href="https://help.shopify.com/en/manual/online-sales-channels/marketplaces/google/getting-setup/product-disapprovals-warnings" rel="noopener noreferrer"&gt;Shopify's own docs&lt;/a&gt; add a number from the merchant's side: an email at least seven days before suspension. Both pages hedge that wording, and neither guarantees delivery.&lt;/p&gt;

&lt;p&gt;There's also a documented exception worth flagging to clients: for an egregious policy issue (misrepresentation is Google's standing example, covered in &lt;a href="https://storecanary.io/blog/merchant-center-misrepresentation-shopify" rel="noopener noreferrer"&gt;our writeup on it&lt;/a&gt;), no warning is given and the account is suspended immediately.&lt;/p&gt;

&lt;p&gt;That's account-level enforcement. A single product failing on data quality is a different path. &lt;a href="https://support.google.com/merchants/answer/13693497?hl=en" rel="noopener noreferrer"&gt;Google's page on product data quality disapprovals&lt;/a&gt; says only that products are regularly reviewed and that disapproved ones won't show on Shopping ads or free listings. No email, no warning period. Per &lt;a href="https://support.google.com/merchants/answer/12153802?hl=en" rel="noopener noreferrer"&gt;Google's Issues in Merchant Center page&lt;/a&gt;, account-level problems show as a banner; item-level problems land in the Needs attention tab under Products and on that product's own detail page. You have to go look.&lt;/p&gt;

&lt;p&gt;That's the split worth remembering: account-level failures push a notification, item-level failures don't. Item-level is also where most real bugs live, like a feed price that disagrees with the live page or an &lt;a href="https://storecanary.io/blog/shopify-merchant-center-availability-mismatch" rel="noopener noreferrer"&gt;availability mismatch between the store and the feed&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Search Console alert you when a product page drops out of the index?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.google.com/webmasters/answer/9130064?hl=en" rel="noopener noreferrer"&gt;Search Console's help page&lt;/a&gt; says it sends messages "when an important event occurs," without listing which events those are. No page documents a per-URL alert for a single product leaving the index.&lt;/p&gt;

&lt;p&gt;The part worth actually checking on a client account: email preferences are set per user, not per property (&lt;a href="https://support.google.com/webmasters/answer/140528?hl=en" rel="noopener noreferrer"&gt;source&lt;/a&gt;). If a former developer or an old agency contact is the only verified user on a property, that's who any alert goes to. Not the client, not you.&lt;/p&gt;

&lt;p&gt;Then there's the age of the data. The &lt;a href="https://support.google.com/webmasters/answer/9012289?hl=en" rel="noopener noreferrer"&gt;URL Inspection tool&lt;/a&gt; states plainly that results come from the last indexed version of the page, not the live one, and that the page "may have changed or become unavailable since Google last saw it." The &lt;a href="https://support.google.com/webmasters/answer/7440203?hl=en" rel="noopener noreferrer"&gt;Page indexing report&lt;/a&gt; adds that indexing is never instant even after a manual crawl request. Everything Search Console shows is a snapshot from Google's last visit, and on a low-traffic client store that visit can be weeks old.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does Shopify admin actually tell you?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://help.shopify.com/en/manual/online-sales-channels/marketplaces/google/product-status" rel="noopener noreferrer"&gt;Shopify's Google &amp;amp; YouTube channel docs&lt;/a&gt; describe it as a sync mechanism: it pushes product data to Merchant Center and reflects back whatever approval state Google sends. That's its whole scope.&lt;/p&gt;

&lt;p&gt;It carries nothing about organic Search. A product can be active, priced, in stock, and syncing cleanly to the channel while its page has a stray noindex tag, a canonical pointing somewhere else, or has simply fallen out of the sitemap. None of that is visible in admin, by construction: admin reports your store's state, and the bug is in what Google made of it afterward (this is the whole premise behind &lt;a href="https://storecanary.io/blog/shopify-phantom-noindex-google" rel="noopener noreferrer"&gt;the phantom noindex problem&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why won't an uptime or diff monitor catch this either?
&lt;/h2&gt;

&lt;p&gt;Pointing a generic uptime check at product URLs doesn't work here, and it's worth knowing exactly why before relying on it. &lt;a href="https://developers.google.com/search/docs/crawling-indexing/block-indexing" rel="noopener noreferrer"&gt;Google's docs on blocking indexing&lt;/a&gt; state that for a noindex rule to work, the page has to still be reachable by the crawler. So the page keeps answering your monitor with the same 200 and the same markup, while Google drops it from Search results entirely.&lt;/p&gt;

&lt;p&gt;Same story for a canonical tag pointing at the wrong URL (one line in the head), a feed price that no longer matches the page (nothing on the page changes), or a product removed from the sitemap (a different file entirely). The page itself never breaks. That's exactly what a content-diff monitor is built to miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually reports each failure?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Merchant Center&lt;/th&gt;
&lt;th&gt;Search Console&lt;/th&gt;
&lt;th&gt;Shopify admin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Account suspended for policy breach&lt;/td&gt;
&lt;td&gt;Warning email + banner&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Egregious policy breach&lt;/td&gt;
&lt;td&gt;Suspended, no warning&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single product disapproved (data quality)&lt;/td&gt;
&lt;td&gt;Needs attention tab&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Channel status only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page starts carrying noindex&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Report, after next crawl&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google picks a different canonical&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Report, after next crawl&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;robots.txt starts blocking products&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Report, after next crawl&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product drops out of the sitemap&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row isn't an oversight in the table. A &lt;a href="https://storecanary.io/blog/shopify-product-missing-from-sitemap" rel="noopener noreferrer"&gt;product that leaves the sitemap&lt;/a&gt; while staying active and purchasable produces no error anywhere. It's the cleanest example of a failure with zero reporting surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you check what you'd actually be alerted to?
&lt;/h2&gt;

&lt;p&gt;Before assuming any of these systems will tell you something broke, it's worth confirming what they'd actually deliver, and to whom.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Search Console → Settings → Users and permissions.&lt;/strong&gt; Confirm the listed emails belong to someone who still reads them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same check inside Merchant Center's user list.&lt;/strong&gt; A policy warning email goes to whoever's on the account, which on an older client is sometimes a contractor who left two years ago.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open the Page indexing report, note the last updated date&lt;/strong&gt;, then run URL Inspection on a live product and compare it against a live test. The gap between the two is how long a change can sit unreported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand-check a handful of important products directly&lt;/strong&gt;: &lt;code&gt;site:client-domain.com&lt;/code&gt; plus the handle, view-source for a noindex tag and the canonical URL, and the product's status inside Merchant Center.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the manual ceiling. Fine for three products before a client call, not something anyone repeats weekly across a full catalogue by hand, which is exactly why so many of these bugs ship inside a theme update or an app install and only get noticed once a client asks where their traffic went.&lt;/p&gt;

&lt;p&gt;How do you currently catch this on client stores: Search Console emails, a manual audit before each release, something scripted?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article — with screenshots and ongoing updates — lives on &lt;a href="https://storecanary.io/blog/google-product-visibility-alerts-shopify" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>googlesearch</category>
    </item>
    <item>
      <title>Discovered, not indexed: the Shopify crawl bug clients hit</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 02 Sep 2026 11:56:15 +0000</pubDate>
      <link>https://dev.to/max_buildogs/discovered-not-indexed-the-shopify-crawl-bug-clients-hit-66j</link>
      <guid>https://dev.to/max_buildogs/discovered-not-indexed-the-shopify-crawl-bug-clients-hit-66j</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Discovered - currently not indexed" means Google found the URL but hasn't fetched it yet; it says nothing about content quality (that's the neighboring "Crawled - currently not indexed" status).&lt;/li&gt;
&lt;li&gt;On Shopify the usual cause is URL inflation: recommendation tracking parameters, collection-scoped product URLs, and app-added query strings that the default robots.txt doesn't block.&lt;/li&gt;
&lt;li&gt;The fix lives in robots.txt.liquid and internal link markup, not in rewriting product copy or spamming "Request indexing."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A client's Shopify store shows a growing pile of URLs stuck under "Discovered - currently not indexed" in Search Console, and nothing in the site throws an error. No noindex tag, no 404, no robots block anyone remembers writing. This status shows up on stores you build and maintain, not just ones you audit after the fact, because it's produced by defaults nobody chose: Shopify's Product Recommendations API appends tracking parameters to product links, collection pages create a second URL for every product they list, and apps bolt on filters and widgets with their own query strings. None of it is flagged as an SEO risk anywhere in the app install flow. This article walks through what the status actually means, why Shopify stores generate far more crawlable URLs than products, and the exact checks (view-source, robots.txt, sitemap counts) you can run against a client's store in about five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does "Discovered - currently not indexed" actually mean?
&lt;/h2&gt;

&lt;p&gt;Google's &lt;a href="https://support.google.com/webmasters/answer/7440203" rel="noopener noreferrer"&gt;Page indexing report documentation&lt;/a&gt; defines it precisely: "The page was found by Google, but not crawled yet. Typically, Google wanted to crawl the URL but this was expected to overload the site; therefore Google rescheduled the crawl."&lt;/p&gt;

&lt;p&gt;Compare that with the status directly beneath it in the same report: "The page was crawled by Google but not indexed. It may or may not be indexed in the future; no need to resubmit this URL for crawling."&lt;/p&gt;

&lt;p&gt;One word apart, and the fixes are opposite. A &lt;strong&gt;crawled&lt;/strong&gt; page has been fetched and read, so its title, markup and content have actually been judged. A &lt;strong&gt;discovered&lt;/strong&gt; page has been seen by nothing at Google. Editing product copy on a page in that state changes nothing, because the change isn't visible to the entity that put it in the queue. The first question to ask on a client's store isn't "what's wrong with these pages," it's "what is the crawler doing instead."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does Google flag this as a crawl-budget signal?
&lt;/h2&gt;

&lt;p&gt;Google opens its &lt;a href="https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget" rel="noopener noreferrer"&gt;large-site crawl budget guide&lt;/a&gt; by narrowing who needs it: "If your site doesn't have a large number of pages that change rapidly, or if your pages seem to be crawled the same day that they are published, you don't need to read this guide." Two of the three qualifying conditions it lists are about raw size (a million pages, or ten thousand changing daily). The third isn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Sites with a large portion of their total URLs classified by Search Console as Discovered - currently not indexed"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Google is naming the status itself as a qualifying condition, independent of catalog size. A four-hundred-product store isn't a large site by any normal reading, but if its Page indexing report is dominated by this status, it's in scope. The real question isn't how many products the client sells, it's how many addresses the store hands out for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does a Shopify store have more URLs than products?
&lt;/h2&gt;

&lt;p&gt;A Shopify store doesn't present one URL per product. It presents the canonical product URL plus every alternative address the platform, the theme, and installed apps are willing to link. Three mechanisms account for most of it, and none is visible in the admin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where do the recommendation tracking parameters come from?
&lt;/h3&gt;

&lt;p&gt;Shopify's &lt;a href="https://shopify.dev/docs/api/ajax/reference/product-recommendations" rel="noopener noreferrer"&gt;Product Recommendations API reference&lt;/a&gt; documents this directly: "the &lt;code&gt;url&lt;/code&gt; property for each &lt;code&gt;product&lt;/code&gt; in the products response contains URL parameters that lets you build a conversion funnel." The example URL in Shopify's own docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/products/gorgeous-wooden-computer?pr_choice=default&amp;amp;pr_prod_strat=description&amp;amp;pr_rec_pid=13&amp;amp;pr_ref_pid=17&amp;amp;pr_seq=alternating
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those parameters exist so Shopify's reports can attribute a sale to a recommendation strategy. The side effect: the same product page gets a distinct address for every combination of referring product and strategy. A &lt;a href="https://community.shopify.com/c/technical-q-a/how-can-i-stop-unwanted-urls-from-being-indexed-in-google/td-p/2489745" rel="noopener noreferrer"&gt;Shopify Community thread from March 2024&lt;/a&gt; describes roughly 80,000 parameter URLs discovered on one store. A &lt;a href="https://ed.codes/blog/weird-shopify-seo-issue-millions-of-indexed-pages" rel="noopener noreferrer"&gt;case study by developer Eduard Fastovski&lt;/a&gt; describes a 200-product store that accumulated over a million indexed URLs (plus several million more not indexed) from &lt;code&gt;?pr_prod&lt;/code&gt; and &lt;code&gt;?filter&lt;/code&gt; parameters; after blocking both patterns: "It worked! Down to just 1k pages now, from 5 million!"&lt;/p&gt;

&lt;p&gt;What lets this persist: &lt;strong&gt;Shopify's default robots.txt blocks the recommendations &lt;em&gt;endpoint&lt;/em&gt;, not the URLs it hands out.&lt;/strong&gt; Checking the live robots.txt of two unrelated Shopify stores shows the standard &lt;code&gt;Disallow: /recommendations/products&lt;/code&gt; rule, and no rule matching &lt;code&gt;pr_prod_strat&lt;/code&gt;, &lt;code&gt;pr_rec_id&lt;/code&gt;, &lt;code&gt;pr_rec_pid&lt;/code&gt;, &lt;code&gt;pr_ref_pid&lt;/code&gt;, or &lt;code&gt;pr_seq&lt;/code&gt;. The Ajax endpoint is closed; every product URL carrying those params, wherever it's linked from (a theme section, an app, a third-party sitemap), stays fully crawlable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do collection-scoped product URLs duplicate every product?
&lt;/h3&gt;

&lt;p&gt;Shopify creates a second address whenever a product card links through its collection: &lt;code&gt;/collections/summer-sale/products/linen-shirt&lt;/code&gt; alongside &lt;code&gt;/products/linen-shirt&lt;/code&gt;. Shopify's own &lt;a href="https://shopify.dev/docs/api/liquid/filters/within" rel="noopener noreferrer"&gt;Liquid reference for the &lt;code&gt;within&lt;/code&gt; filter&lt;/a&gt; warns: "Because a standard product page and a product page in the context of a collection have the same content on separate URLs, you should consider the SEO implications of using the within filter."&lt;/p&gt;

&lt;p&gt;A product sitting in six collections can be reached at seven addresses. The default robots.txt blocks sorted and filtered collection URLs (&lt;code&gt;Disallow: /collections/*sort_by*&lt;/code&gt;, &lt;code&gt;Disallow: /collections/*+*&lt;/code&gt;), but nothing blocks &lt;code&gt;/collections/*/products/*&lt;/code&gt;. Those URLs carry a canonical tag pointing back at the clean product URL, which is correct, but a canonical is only readable after a fetch. Consolidating them still costs a crawl each.&lt;/p&gt;

&lt;h3&gt;
  
  
  What URLs are your client's apps adding without telling anyone?
&lt;/h3&gt;

&lt;p&gt;The default file blocks named patterns, not parameters in general: &lt;code&gt;oseid&lt;/code&gt;, &lt;code&gt;preview_theme_id&lt;/code&gt;, &lt;code&gt;preview_script_id&lt;/code&gt;, a specific duplicated &lt;code&gt;ls=&lt;/code&gt; shape, sorted/filtered collections, and &lt;code&gt;/search&lt;/code&gt;. Any parameter an app or theme introduces outside those patterns is crawlable by default: a filter app with its own query format, a "recently viewed" widget, a wishlist link, a campaign parameter that leaks into an internal link instead of staying outbound-only. No app declares its URL footprint at install time, so this is invisible until it shows up in Search Console weeks later.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you check a client's store in five minutes?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the shapes, not the count.&lt;/strong&gt; Open Page indexing, select &lt;em&gt;Discovered - currently not indexed&lt;/em&gt;, and bucket the sample URLs: clean &lt;code&gt;/products/…&lt;/code&gt;, collection-scoped &lt;code&gt;/collections/…/products/…&lt;/code&gt;, and anything with a &lt;code&gt;?&lt;/code&gt;. The proportions tell you which mechanism above is dominant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare what Google knows against what the sitemap declares.&lt;/strong&gt; Add the indexed and not-indexed totals, then open &lt;code&gt;yourstore.com/sitemap.xml&lt;/code&gt;. Shopify auto-generates it with &lt;a href="https://help.shopify.com/en/manual/promoting-marketing/seo/find-site-map" rel="noopener noreferrer"&gt;separate child sitemaps&lt;/a&gt; for products, collections, blogs, and pages. If Google knows an order of magnitude more URLs than the sitemap declares, the surplus arrived through internal links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View source on a product page and search for &lt;code&gt;pr_prod_strat&lt;/code&gt;.&lt;/strong&gt; If the theme renders a recommendations block into the page, the parameters sit in the &lt;code&gt;href&lt;/code&gt; attributes. Search for &lt;code&gt;pr_rec_pid&lt;/code&gt; too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read &lt;code&gt;yourstore.com/robots.txt&lt;/code&gt; properly.&lt;/strong&gt; Check whether anything blocks the parameter shapes from step 3, and whether a past edit went further than intended. Shopify's &lt;a href="https://help.shopify.com/en/manual/promoting-marketing/seo/editing-robots-txt" rel="noopener noreferrer"&gt;own help page&lt;/a&gt; warns that "Incorrect use of the feature can result in loss of all traffic."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect one clean product URL, not fifty.&lt;/strong&gt; Run URL Inspection and note the last crawl date. Google's &lt;a href="https://support.google.com/webmasters/answer/9012289" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; states this "is not a live test" and that there's "a daily limit of inspection requests for each property." One URL is a diagnosis; a full catalog isn't auditable this way.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How do you fix each cause?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Parameter URLs.&lt;/strong&gt; Two real options, with a trade-off. Strip the parameters at the theme level (loses the conversion-funnel attribution those params exist for), or block the shapes in &lt;code&gt;robots.txt.liquid&lt;/code&gt; (keeps tracking working for real visitors, stops Google from fetching them). Google's guidance is explicit about method: "Block crawling of URLs using robots.txt," and separately, "Don't use noindex, as Google will still request, but then drop the page when it sees a noindex meta tag or header in the HTTP response, wasting crawling time." A noindex on a parameter URL is the intuitive fix and it makes the crawl problem worse. Edit &lt;code&gt;robots.txt.liquid&lt;/code&gt; carefully and re-check it after theme updates, since it's theme code and a theme change can silently overwrite your edit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collection-scoped duplicates.&lt;/strong&gt; Where a product card doesn't need collection context, link to the plain product URL instead of the collection-scoped one. Google's crawl guidance: "Eliminate duplicate content to focus crawling on unique content rather than unique URLs."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Products that are genuinely gone.&lt;/strong&gt; Return a 404 or 410, per Google's guidance, rather than redirecting a deleted product's URL to something convenient, which just keeps a competing URL alive in the queue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server response.&lt;/strong&gt; Google's own definition of the status mentions overloading the site as a possible cause. Efficient page loads, HTTP caching, and 304 responses all matter here; heavy third-party scripts and app-injected assets are usually the biggest levers you actually control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What not to do:&lt;/strong&gt; don't work down the list requesting indexing on every URL. Google states "Submitting a request does not guarantee that the page will appear in the Google Index," and the daily limits make a catalog-sized pass impossible anyway. It's a diagnostic tool, not a remedy, and it leaves the underlying crawl surface untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this bug keep coming back after you fix it?
&lt;/h2&gt;

&lt;p&gt;Every mechanism above comes from ordinary store maintenance, not a one-time mistake. A merchandiser adds three collections and every product in them gains three addresses. A theme update ships a new recommendations section. An app adds a filter with a query format nobody reviewed. A seasonal range gets deleted and redirected instead of allowed to 404. None of it announces itself, and the crawl surface only grows unless someone deliberately shrinks it. Worth building a recurring check into your maintenance routine (robots.txt diff after theme updates, periodic sitemap-vs-indexed count) rather than treating this as a one-time cleanup.&lt;/p&gt;

&lt;p&gt;Has anyone found other URL shapes leaking through a Shopify store's default robots.txt, beyond the recommendation params and collection-scoped duplicates covered here?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article — with screenshots and ongoing updates — lives on &lt;a href="https://storecanary.io/blog/discovered-currently-not-indexed-shopify" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>googlesearch</category>
    </item>
    <item>
      <title>The Shopify structured data bug that gets clients suspended</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 31 Aug 2026 11:56:45 +0000</pubDate>
      <link>https://dev.to/max_buildogs/the-shopify-structured-data-bug-that-gets-clients-suspended-4l62</link>
      <guid>https://dev.to/max_buildogs/the-shopify-structured-data-bug-that-gets-clients-suspended-4l62</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Merchant Center suspends an account for "Misrepresentation" without naming a product and without a warning period; Google's own policy states accounts are "suspended upon detection and without prior warning."&lt;/li&gt;
&lt;li&gt;The policy has a clause unrelated to business identity: it forbids offering products you can't deliver and points directly at the availability and price attributes in Google's product data specification.&lt;/li&gt;
&lt;li&gt;On a Shopify build, those two attributes come from four separate mechanisms (theme, the &lt;code&gt;structured_data&lt;/code&gt; Liquid filter, any app injecting its own markup, and the feed), and nothing forces them to agree.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build or maintain Shopify stores for clients, this is the bug report you never get. Merchant Center suspends an account for "Misrepresentation," with no product name, no line item, no warning period, just Shopping ads and free listings dropping to zero. Most of what ranks for this error points at business identity: refund policy, contact page, real business name at checkout. Worth checking, genuinely required, and not the whole story.&lt;/p&gt;

&lt;p&gt;The same policy also forbids offering products a shopper can't actually buy, and it refers directly to two feed attributes: availability and price. On a Shopify build those values come from four different places, and nothing reconciles them automatically. This piece covers where that agreement breaks in a typical Shopify build, how to audit a client's store for it in a few minutes, and what to fix before requesting Google's re-review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why doesn't Merchant Center name the product that broke the rule?
&lt;/h2&gt;

&lt;p&gt;Most Merchant Center issues arrive with a warning period and specific examples. &lt;a href="https://support.google.com/merchants/answer/2948694" rel="noopener noreferrer"&gt;Google's documentation on warnings and suspensions&lt;/a&gt; describes products continuing to appear "however their performance may be limited," with emails pointing at what to fix. Misrepresentation doesn't work that way. Its &lt;a href="https://support.google.com/merchants/answer/6150127" rel="noopener noreferrer"&gt;policy page&lt;/a&gt; classifies these violations as egregious and states that accounts "will be suspended upon detection and without prior warning." Shopify's own &lt;a href="https://help.shopify.com/en/manual/online-sales-channels/google/getting-setup/product-disapprovals-warnings" rel="noopener noreferrer"&gt;page on Google disapprovals and warnings&lt;/a&gt; lists this under "Unacceptable business practices" and notes that "Deadline extensions aren't granted for any violation."&lt;/p&gt;

&lt;p&gt;So there's no missing notification setting to find. The enforcement acts first and explains later, which leaves you reading the policy text itself instead of an email with an example. A &lt;a href="https://community.shopify.com/t/a-shopify-store-owner-is-struggling-with-a-persistent-google-merchant-center-misrepresentation-suspension/590225" rel="noopener noreferrer"&gt;Shopify Community thread opened in February 2026&lt;/a&gt; and still active months later captures it directly: Google "continues to flag my account for 'Misrepresentation' without providing specific details on what's wrong or how to fix it."&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the misrepresentation policy actually require?
&lt;/h2&gt;

&lt;p&gt;Most guides for this suspension point at business identity. Shopify's &lt;a href="https://help.shopify.com/en/manual/online-sales-channels/marketplaces/google/requirements" rel="noopener noreferrer"&gt;requirements page for the Google &amp;amp; YouTube channel&lt;/a&gt; spells it out: refund policy and terms of service "available in your footer menu," plus contact information "visible to customers" via "at least one contact method such as email address, phone number, mailing address, or a contact form." Check that first, it's cheap and well documented.&lt;/p&gt;

&lt;p&gt;But the policy has a second clause with nothing to do with identity: "Offer products that you don't have or can't deliver" is listed as an unacceptable business practice, and the unavailable-offers section forbids "Promising products or promotional offers that aren't available for users" (examples: promoting unstocked products, expired deals, or a call-to-action that "isn't easily achievable from the landing page"). The policy then refers you to the product data specification's availability and price attributes for "specific guidelines to comply with this policy."&lt;/p&gt;

&lt;p&gt;That &lt;a href="https://support.google.com/merchants/answer/7052112" rel="noopener noreferrer"&gt;specification&lt;/a&gt; asks you to "accurately submit the product's availability and match the availability from your landing page, checkout pages, and structured data," and the same for price. Four surfaces (feed, landing page, structured data, checkout) have to agree with each other and with what you can actually ship. On a Shopify store those four are produced by four different mechanisms, and nothing reconciles them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does a Shopify build break that requirement?
&lt;/h2&gt;

&lt;p&gt;These are mechanisms that can make the four surfaces disagree, not proof of what triggered any specific suspension. Google doesn't publish which signal caused a given account action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continue selling when out of stock
&lt;/h3&gt;

&lt;p&gt;Shopify's &lt;a href="https://help.shopify.com/en/manual/products/inventory/setup/selling-when-out-of-stock" rel="noopener noreferrer"&gt;docs on selling out-of-stock products&lt;/a&gt; describe this setting plainly: enable it in the Inventory section of a product or variant, and customers can buy at zero inventory or below. It's a legitimate setting for stock arriving soon or inventory tracked elsewhere, but with it on, every surface agrees on the wrong answer. The page offers the item, add-to-cart works, the structured data reports it available, the feed says in stock. Nothing is internally inconsistent, the store is just promising goods it doesn't hold, and the spec asks for availability that's accurate, not merely consistent.&lt;/p&gt;

&lt;p&gt;The setting is per-variant, too. Toggle it on during a stockout, sell through, restock, and it stays on for that one variant with nobody noticing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Duplicate or stale structured data
&lt;/h3&gt;

&lt;p&gt;Shopify generates Product markup through the Liquid &lt;a href="https://shopify.dev/docs/api/liquid/filters/structured_data" rel="noopener noreferrer"&gt;&lt;code&gt;structured_data&lt;/code&gt; filter&lt;/a&gt;, which emits an offer object like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"offers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/products/crocodile-tears?variant=39888242344001#offer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Offer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"availability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://schema.org/OutOfStock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"56.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priceCurrency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CAD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://.../products/crocodile-tears?variant=39888242344001"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a stock theme that availability value tracks the variant correctly. It breaks the moment a second &lt;code&gt;Product&lt;/code&gt; block enters the page, from a review app, a rich-snippet app, or a theme section with a hardcoded &lt;code&gt;InStock&lt;/code&gt; value, and Google reads a claim the store never intended to make. This is the account-level version of a mismatch covered in more detail at the item level, in &lt;a href="https://storecanary.io/blog/shopify-merchant-center-availability-mismatch" rel="noopener noreferrer"&gt;the availability mismatch between Shopify and Google Shopping&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  One offer, many prices
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;structured_data&lt;/code&gt; filter emits a single offer, for a single variant, with a single price. A product whose variants span a wide range declares only one of those prices in its markup. A shopper landing on a different variant sees a different number than the one Google received, and the spec requires price to match the landing page, structured data, and checkout. Related failure modes: &lt;a href="https://storecanary.io/blog/merchant-center-price-mismatch-shopify" rel="noopener noreferrer"&gt;the on-page price mismatch trap&lt;/a&gt; and &lt;a href="https://storecanary.io/blog/invalid-value-category-price-shopify" rel="noopener noreferrer"&gt;a price of zero reaching Google&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redirects on discontinued product URLs
&lt;/h3&gt;

&lt;p&gt;The unavailable-offers clause also covers the landing page itself: a call-to-action that "isn't easily achievable from the landing page." On Shopify this usually means an out-of-stock or discontinued product URL redirecting to a collection or the homepage instead of returning the product page, so a shopper who clicks through from a listing can't reach the item at all. More detail on that failure mode: &lt;a href="https://storecanary.io/blog/shopify-product-page-redirect-merchant-center" rel="noopener noreferrer"&gt;when a product page redirects to your homepage&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you audit a client's store in five minutes?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Copy the exact issue name from the policy area of Merchant Center. An account-level misrepresentation issue reads differently from a product-level disapproval, and the fix differs too.&lt;/li&gt;
&lt;li&gt;Pick five out-of-stock products and compare four surfaces for each: what the feed reports, what the page shows, the &lt;code&gt;availability&lt;/code&gt; value in the page's JSON-LD, and what happens on add-to-cart.&lt;/li&gt;
&lt;li&gt;Check the Continue selling setting on the Inventory section of each product or variant.&lt;/li&gt;
&lt;li&gt;View source on a product with a wide variant range and read the price inside the &lt;code&gt;offers&lt;/code&gt; object; compare it to the cheapest and most expensive variant.&lt;/li&gt;
&lt;li&gt;Open a discontinued product's URL directly and check whether it redirects instead of returning the product page.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How do you fix each issue?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Overselling.&lt;/strong&gt; Decide deliberately which products may oversell, and turn the setting off everywhere else. For products that genuinely ship later, use the spec's &lt;code&gt;preorder&lt;/code&gt; or &lt;code&gt;backorder&lt;/code&gt; availability values instead of claiming stock that isn't there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Duplicate markup.&lt;/strong&gt; Find the second &lt;code&gt;Product&lt;/code&gt; block before editing the first. It's usually coming from an app, and removing the app's block is easier than trying to make the theme's output match it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price coverage.&lt;/strong&gt; If variants span a wide range, the real fix is markup that declares the range instead of a single figure. Until that's built, know which products are affected before touching the theme.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redirects.&lt;/strong&gt; Let a product that no longer sells keep returning its own page (out of stock, priced) instead of redirecting away. The spec notes that even when a product is out of stock, "the price must still be clearly visible on the landing page," which only works if the page still exists.&lt;/p&gt;

&lt;p&gt;Then request the review, not before. Google's docs and Shopify's both give the same figure: reviews "can take up to 7 days to complete." A re-review on a store that hasn't actually changed spends a week to land back where it started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this bug keep coming back after launch?
&lt;/h2&gt;

&lt;p&gt;Every mechanism above comes from ordinary work, not a code review failure. A stockout on a Friday turns on Continue selling, and the restock doesn't turn it back off. A new reviews app adds a second markup block. A supplier drops a line and someone redirects the URL to keep things tidy. A variant gets added well above the price sitting in the markup. None of it throws an error in Shopify admin, and none of it throws one in Merchant Center either, because for this policy Google doesn't warn, it suspends.&lt;/p&gt;

&lt;p&gt;It's worth being precise about what an automated scan can actually tell you here. The misrepresentation policy covers business identity, intent, and whether a merchant can really ship what's listed, none of which is readable from public pages. What is readable is the product-data half the policy points at: a page whose markup claims &lt;code&gt;InStock&lt;/code&gt; while every variant shows sold out, or a page whose declared price contradicts what the page itself renders. That's one input into an account-level judgment, not the judgment itself. Treat anything sold to you as a "misrepresentation checker" with the same skepticism.&lt;/p&gt;

&lt;p&gt;Have you shipped a Shopify feature that quietly broke a client's availability or price data without throwing an error? What caught it: an audit, a client complaint, or a Merchant Center suspension?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article — with screenshots and ongoing updates — lives on &lt;a href="https://storecanary.io/blog/merchant-center-misrepresentation-shopify" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>The Shopify structured data bug your JSON-LD checks miss</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Thu, 27 Aug 2026 11:56:07 +0000</pubDate>
      <link>https://dev.to/max_buildogs/the-shopify-structured-data-bug-your-json-ld-checks-miss-4e9j</link>
      <guid>https://dev.to/max_buildogs/the-shopify-structured-data-bug-your-json-ld-checks-miss-4e9j</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search Console's "unparsable structured data" means a syntax error made Google discard the &lt;em&gt;entire&lt;/em&gt; markup block, not just one bad field, and Google is explicit that every item in that report is a critical error, never a warning.&lt;/li&gt;
&lt;li&gt;On Shopify, the usual causes are three specific mistakes: a trailing comma, unescaped characters (quotes, line breaks) inside a product title or description, or two JSON-LD objects concatenated in the same &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag without an array wrapper.&lt;/li&gt;
&lt;li&gt;You can tell whether the theme or an app produced the broken block by copying a text fragment from it and searching for that fragment in &lt;strong&gt;Online Store → Themes → Edit code&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build or maintain Shopify storefronts for clients, you've probably seen this line in Search Console and had no clean way to trace it: "unparsable structured data." It's one of the few reports where Google states severity plainly, every item listed is a critical failure, not a warning. The frustrating part is that nothing on the rendered page looks broken. No console error, no visual regression, no failed build. A single stray character in a Liquid snippet, or a JSON-LD block an app injects at runtime, quietly invalidates the whole document, and the client's product schema (price, availability, rating) disappears from how Google reads the page, with no trace in a normal QA pass.&lt;/p&gt;

&lt;p&gt;This post covers the three syntax errors that actually cause this on Shopify, how to figure out whether it's your theme code or a third-party app, and how to fix and revalidate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does "unparsable structured data" actually mean?
&lt;/h2&gt;

&lt;p&gt;Google's own definition is unusually direct. The report &lt;a href="https://support.google.com/webmasters/answer/9166415" rel="noopener noreferrer"&gt;lists structured data that "could not be parsed because of a serious syntax error"&lt;/a&gt;, and "the intended type of structured data (Job, Event, and so on) could not be determined because of the parsing error." Google isn't flagging a schema mismatch here. It saw a markup block, failed to parse it as JSON, and couldn't even tell what it was supposed to describe.&lt;/p&gt;

&lt;p&gt;The same page adds a detail worth building process around: "All items in this report are critical structured data errors; there are no warnings or valid items." There's no severity triage to do. Anything that shows up here is broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the client actually lose when a block fails to parse?
&lt;/h2&gt;

&lt;p&gt;The page stays indexed and keeps ranking on its text content. What disappears is everything that specific block declared. If the failing block was the &lt;code&gt;Product&lt;/code&gt; markup, the price, availability, and rating it carried are simply absent as far as Google is concerned, and the page loses eligibility for the rich result it used to qualify for. Where a Merchant Center feed depends on on-page data, the product can drop out of that feed too.&lt;/p&gt;

&lt;p&gt;This is why the bug usually surfaces sideways: someone notices review stars vanished from a listing, or a client asks why a competitor's price shows up in search but theirs doesn't, and the root cause traces back to a schema block that stopped parsing weeks earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which syntax errors cause this on Shopify themes?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Two JSON documents concatenated.&lt;/strong&gt; The theme writes one &lt;code&gt;Product&lt;/code&gt; object into a &lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;&lt;/code&gt; tag, and an app appends a second object into the same tag with nothing separating them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Product&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wool Beanie&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AggregateRating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ratingValue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4.5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two valid objects placed back to back are not valid JSON. Both get discarded. The fix is either separate &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags or wrapping both in a &lt;code&gt;@graph&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A trailing comma.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Offer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"24.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priceCurrency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That trailing comma after &lt;code&gt;"USD"&lt;/code&gt; invalidates the entire document, not just that line. It's the single most common hand-edit mistake in a Liquid snippet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unescaped characters inside a value.&lt;/strong&gt; A product title containing a straight double quote or a line break, interpolated into JSON without escaping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;"name": "&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;product.title&lt;/code&gt; is &lt;code&gt;Men's "Classic" Wool Beanie&lt;/code&gt;, that renders broken JSON. This one is the sneakiest because it only breaks on the products whose text happens to contain the offending character, so it looks intermittent until you spot the pattern. The reliable fix is Shopify's &lt;code&gt;json&lt;/code&gt; filter instead of raw interpolation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;"name": &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;| json&lt;/code&gt; handles escaping for you and also gets you correct quoting (no manual &lt;code&gt;".."&lt;/code&gt; wrapper needed).&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you find whether the theme or an app injected the broken block?
&lt;/h2&gt;

&lt;p&gt;Once a validator has shown you the failing block, copy a distinctive text fragment from it and search for that fragment in &lt;strong&gt;Online Store → Themes → Edit code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the fragment is in the theme files, the theme is generating the block and you can fix it directly. If it appears on the rendered page but nowhere in the theme source, an app is injecting it at runtime, editing theme code won't touch it. The fix in that case is in the app's own settings (most reviews, SEO, and social apps have a toggle for structured data output) or a support ticket with the app vendor.&lt;/p&gt;

&lt;p&gt;This distinction matters for maintenance, not just for the immediate fix. A theme fix stays fixed until someone edits the theme again. An app fix can be silently undone by the app's next update, so it's worth re-checking after any app update rather than treating it as a one-time fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you check and fix it end to end?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Paste the affected product URL into &lt;a href="https://validator.schema.org" rel="noopener noreferrer"&gt;validator.schema.org&lt;/a&gt; and read the exact parsing error, plus which block it belongs to.&lt;/li&gt;
&lt;li&gt;Copy a unique fragment from that block and search for it in &lt;strong&gt;Online Store → Themes → Edit code&lt;/strong&gt; to determine theme vs. app.&lt;/li&gt;
&lt;li&gt;If it's the theme: remove the trailing comma, switch raw interpolation to &lt;code&gt;| json&lt;/code&gt;, and wrap multiple objects in an array instead of concatenating them.&lt;/li&gt;
&lt;li&gt;If it's an app: disable its structured data injection in its settings, or send its support team the validator output.&lt;/li&gt;
&lt;li&gt;Revalidate on validator.schema.org, cross-check with the &lt;a href="https://search.google.com/test/rich-results" rel="noopener noreferrer"&gt;Rich Results Test&lt;/a&gt;, then open the error in Search Console and click &lt;strong&gt;Validate Fix&lt;/strong&gt;. Google notes validation "typically takes up to about two weeks, but in some cases can take much longer," the fix itself is live immediately, only the report is slow to catch up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the symptom is invisible on the page, the useful habit isn't checking when something looks wrong, it's checking after any change that touches the theme or the app list. A theme update, a new reviews app, a bulk product import: any of those can introduce a stray character into a block that had parsed cleanly for a year.&lt;/p&gt;

&lt;p&gt;Have you traced one of these back to a specific app before? Curious which ones are the repeat offenders in your stack.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article, with screenshots and ongoing updates, lives on &lt;a href="https://storecanary.io/blog/unparsable-structured-data-shopify" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Shopify Product schema gap that breaks shippingDetails</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 26 Aug 2026 11:56:54 +0000</pubDate>
      <link>https://dev.to/max_buildogs/the-shopify-product-schema-gap-that-breaks-shippingdetails-2gi3</link>
      <guid>https://dev.to/max_buildogs/the-shopify-product-schema-gap-that-breaks-shippingdetails-2gi3</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shopify's built-in Liquid &lt;code&gt;structured_data&lt;/code&gt; filter never emits &lt;code&gt;shippingDetails&lt;/code&gt; or &lt;code&gt;hasMerchantReturnPolicy&lt;/code&gt; inside the &lt;code&gt;Offer&lt;/code&gt; object, on any theme, out of the box. It's a filter limitation, not a misconfiguration on a specific client site.&lt;/li&gt;
&lt;li&gt;Both fields are "recommended," not "required." Search Console still lists the product as valid; the only thing missing is the shipping/returns annotation on the listing itself.&lt;/li&gt;
&lt;li&gt;Google reads shipping/returns data from four sources in strict precedence (Content API for Shopping, then Merchant Center/Search Console settings, then product-level markup, then Organization markup). Pasting a snippet into a client's theme often changes nothing if a higher-priority source is already set, and only a Shopify store's "General" shipping profile syncs correctly to Merchant Center.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build or maintain Shopify stores for clients, you've probably run into this Search Console warning: &lt;code&gt;shippingDetails&lt;/code&gt; and &lt;code&gt;hasMerchantReturnPolicy&lt;/code&gt; missing from Merchant listings, across an entire catalog, on a store you just launched. It's not a content problem on the client's end. Shopify's built-in Liquid &lt;code&gt;structured_data&lt;/code&gt; filter never emits these fields in the &lt;code&gt;Offer&lt;/code&gt; object, on any theme, by default. The usual fix, pasting a shipping/returns block into the product template, is a real fix for a real symptom, but it often targets a layer Google isn't even reading. This piece covers why the fields are absent, why they don't actually invalidate a product, the four-source precedence order Google uses for shipping and returns data, a Shopify-specific shipping profile trap that silently desyncs rates from Merchant Center, and a five-minute audit you can run on any client account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why doesn't Shopify's offer markup include shipping and returns fields?
&lt;/h2&gt;

&lt;p&gt;Shopify generates product JSON-LD through the Liquid &lt;a href="https://shopify.dev/docs/api/liquid/filters/structured_data" rel="noopener noreferrer"&gt;&lt;code&gt;structured_data&lt;/code&gt; filter&lt;/a&gt;, and its documented output is short. Inside &lt;code&gt;offers&lt;/code&gt; it emits exactly six properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"offers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/products/crocodile-tears?variant=39888242344001#offer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Offer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"availability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://schema.org/OutOfStock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"56.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priceCurrency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CAD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://…/products/crocodile-tears?variant=39888242344001"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;shippingDetails&lt;/code&gt;, no &lt;code&gt;hasMerchantReturnPolicy&lt;/code&gt;, no &lt;code&gt;priceValidUntil&lt;/code&gt;. This isn't something you find and undo in the theme code: it's absent by construction on any theme using the built-in filter, which offers no option to add it. That's why the warning covers a client's whole catalog on day one, on a store nobody has customized yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does this actually invalidate the product for search?
&lt;/h2&gt;

&lt;p&gt;No. Google's &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/merchant-listing" rel="noopener noreferrer"&gt;merchant listing structured data docs&lt;/a&gt; list both fields as recommended properties of &lt;code&gt;Offer&lt;/code&gt;, not required ones. The required set is small: &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;offers&lt;/code&gt; on the product, plus a price and currency inside the offer.&lt;/p&gt;

&lt;p&gt;Search Console's &lt;a href="https://support.google.com/webmasters/answer/7552505" rel="noopener noreferrer"&gt;rich result reports&lt;/a&gt; reflect that split directly: critical issues sit in a table called "Why items are invalid," non-critical ones in "Improve item appearance." Google's own definition: "A valid item is an item that doesn't have any critical issues and can appear on Google as a rich result." A product flagged only for these two fields stays valid and eligible.&lt;/p&gt;

&lt;p&gt;So the real cost is narrower than the warning implies: the client loses the delivery/returns annotation Google can attach to the listing, the kind sitting under a competitor's product. That's a different failure class from a block Google can't parse at all, which gets discarded outright along with everything inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  If I paste the markup into the theme, why does nothing change on the listing?
&lt;/h2&gt;

&lt;p&gt;Because Google reads shipping and returns data from four sources, in a fixed order of precedence, per its docs for &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/return-policy" rel="noopener noreferrer"&gt;return policy&lt;/a&gt; and &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/shipping-policy" rel="noopener noreferrer"&gt;shipping policy&lt;/a&gt; structured data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Content API for Shopping account-level settings&lt;/li&gt;
&lt;li&gt;Settings in Merchant Center or Search Console&lt;/li&gt;
&lt;li&gt;Product-level merchant listing markup (the snippet you'd paste into the theme)&lt;/li&gt;
&lt;li&gt;Organization-level markup&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Google states the consequence directly: "if you provide both return policy markup on your site and return policy settings in Search Console, Google will only use the information provided in Search Console." The shipping docs say the same about shipping.&lt;/p&gt;

&lt;p&gt;If the store is connected to Merchant Center, which the Google &amp;amp; YouTube sales channel requires, level 2 is already occupied, and the theme edit only reaches level 3. It's not wasted work: it clears the Search Console warning, it helps any engine reading the page directly, and it's the only option for a store with neither Merchant Center nor Search Console settings configured. But it explains a pattern that looks like a bug and isn't one: the snippet goes in, the warning clears, and the actual listing doesn't move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does Google actually want this markup, if you implement it at all?
&lt;/h2&gt;

&lt;p&gt;Not where most implementations put it. Google's own recommendation, repeated for both fields on the merchant listing page: "We recommend you provide a global shipping policy for your business under &lt;code&gt;Organization&lt;/code&gt; markup instead." A standard, business-wide policy belongs in &lt;code&gt;Organization&lt;/code&gt; markup (&lt;code&gt;ShippingService&lt;/code&gt; under &lt;code&gt;hasShippingService&lt;/code&gt;, &lt;code&gt;MerchantReturnPolicy&lt;/code&gt; for returns); per-product &lt;code&gt;Offer&lt;/code&gt; markup exists to override that default for a product that genuinely differs, and it supports a smaller set of properties than the Organization-level version.&lt;/p&gt;

&lt;p&gt;The common Shopify pattern, a shipping block duplicated into every product page by a Liquid loop, is the exception case implemented as the default, at the lowest-priority level.&lt;/p&gt;

&lt;p&gt;There's also a newer path, from Google's November 2025 post &lt;a href="https://developers.google.com/search/blog/2025/11/more-ways-to-share-shipping" rel="noopener noreferrer"&gt;"More ways to share your shipping and returns policies with Google"&lt;/a&gt;: set the policies directly in Search Console under &lt;strong&gt;Settings &amp;gt; Shopping &amp;gt; Shipping and returns&lt;/strong&gt;. Its &lt;a href="https://support.google.com/webmasters/answer/14907594" rel="noopener noreferrer"&gt;help page&lt;/a&gt; states "You don't need to create a Merchant Center account to configure shipping and returns," but also that policies "created or edited in Merchant Center won't be available in Search Console, and can only be managed in Merchant Center." Once a client connects the Google &amp;amp; YouTube channel, that door closes behind them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does the shipping rate Google shows differ from what the client charges?
&lt;/h2&gt;

&lt;p&gt;This is the Shopify-specific trap underneath the fix, documented by Shopify rather than Google. From the &lt;a href="https://help.shopify.com/en/manual/online-sales-channels/google/requirements" rel="noopener noreferrer"&gt;Google &amp;amp; YouTube channel requirements&lt;/a&gt;: "Only shipping rates in your General shipping profile can sync to Google Merchant Center. If you use custom shipping profiles, then rates will sync incorrectly with Google Merchant Center and cause errors."&lt;/p&gt;

&lt;p&gt;Custom shipping profiles are ordinary store maintenance: one for oversized items, one for a dropshipped line, one for a supplier with different delivery times. The moment a product moves into one, the rate Google receives stops matching checkout. Nothing is technically wrong in the store; the rates just aren't the ones being synced.&lt;/p&gt;

&lt;p&gt;Worth checking before touching anything else: Merchant Center can infer a return policy without an explicit upload, shown as a &lt;a href="https://support.google.com/merchants/answer/14011730" rel="noopener noreferrer"&gt;"For most items"&lt;/a&gt; annotation. A client's listings may already display returns info while Search Console keeps flagging the field missing from theme markup, this whole article's argument compressed into one symptom.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you audit this on a client's store in five minutes?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the actual markup.&lt;/strong&gt; View source on a product page, find the JSON-LD block, look inside &lt;code&gt;offers&lt;/code&gt;. Six properties and nothing about shipping or returns means the store is on the stock filter output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check which table the warning sits in.&lt;/strong&gt; Merchant listings report in Search Console: "Improve item appearance" means non-critical; "Why items are invalid" means something else is broken.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find the authoritative source.&lt;/strong&gt; Is a Merchant Center account connected via the Google &amp;amp; YouTube channel? Is there already a policy under Settings &amp;gt; Shopping &amp;gt; Shipping and returns in Search Console? Whichever exists outranks theme markup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare the synced rate against the real one.&lt;/strong&gt; Check Merchant Center's shipping settings against what a shopper is actually charged at checkout for the same product and destination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the Shopify shipping profiles.&lt;/strong&gt; Settings &amp;gt; Shipping and delivery. Any product sitting in a custom profile instead of General is a rate Google isn't receiving correctly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's the actual fix, and does it depend on the client's setup?
&lt;/h2&gt;

&lt;p&gt;If they use the Google &amp;amp; YouTube channel: set shipping and returns in Merchant Center and treat it as the source of truth. Keep the rates Google needs in the General shipping profile, or enter them manually in Merchant Center instead of importing (safer once custom profiles exist). Theme markup on top of this is cosmetic, not the fix.&lt;/p&gt;

&lt;p&gt;If there's no Merchant Center account: use Settings &amp;gt; Shopping &amp;gt; Shipping and returns in Search Console. It outranks markup and is far less fragile than theme code.&lt;/p&gt;

&lt;p&gt;If you're implementing the markup anyway (for engines reading the page directly, or because neither of the above exists): declare the standard policy once, sitewide, in &lt;code&gt;Organization&lt;/code&gt; markup, and reserve per-product &lt;code&gt;Offer&lt;/code&gt; markup for genuine deviations. Whatever gets declared has to match what the client actually charges. A hardcoded "free shipping" block on a store that charges for shipping is worse than the original warning: it's a promise to the shopper that checkout then breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this keep coming back after you close the ticket?
&lt;/h2&gt;

&lt;p&gt;Four sources, a documented precedence order between them, and no single screen that tells you which one Google used today. A theme update replaces the file holding the markup. A new supplier shows up and someone creates a custom shipping profile. Someone edits rates in Merchant Center and the store's live rates drift from them. Each change is routine, done correctly, in isolation, and none of it throws an error in Shopify admin.&lt;/p&gt;

&lt;p&gt;Worth flagging to clients as ongoing monitoring rather than a one-time fix: a product missing &lt;code&gt;shippingDetails&lt;/code&gt; and &lt;code&gt;hasMerchantReturnPolicy&lt;/code&gt; is informational and still sellable, but worth a periodic recheck instead of a single pull request.&lt;/p&gt;

&lt;p&gt;Have you run into this on a client's store, and did fixing Merchant Center settings actually change what shoppers saw, or just clear the warning? What caught it for you, a client complaint or a routine audit?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article — with screenshots and ongoing updates — lives on &lt;a href="https://storecanary.io/blog/shopify-shippingdetails-hasmerchantreturnpolicy" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>googlesearch</category>
    </item>
    <item>
      <title>The Shopify Search Console statuses that mean you shipped a bug</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Tue, 25 Aug 2026 11:57:04 +0000</pubDate>
      <link>https://dev.to/max_buildogs/the-shopify-search-console-statuses-that-mean-you-shipped-a-bug-9d4</link>
      <guid>https://dev.to/max_buildogs/the-shopify-search-console-statuses-that-mean-you-shipped-a-bug-9d4</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search Console's Page indexing report has 18 documented statuses: 15 reasons a page wasn't indexed, 2 warnings on pages indexed anyway, and 1 clean pass.&lt;/li&gt;
&lt;li&gt;On a Shopify build, most of that list (redirects, alternate canonicals, robots.txt blocks on /cart or /checkout) is normal duplicate-URL handling, not a bug.&lt;/li&gt;
&lt;li&gt;A handful of statuses (&lt;code&gt;noindex&lt;/code&gt;, wrong robots.txt rule, wrong canonical, soft 404) mean your theme, app, or migration broke indexing on a live product page, and nothing in Shopify admin will tell you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build or maintain Shopify stores for clients, you've probably had this conversation: the client says "we're not showing up on Google anymore," you open Search Console, and the Page indexing report shows hundreds of "Not indexed" URLs. Before you panic and start debugging, you need to know that most of that number is Shopify behaving exactly as designed: variant URLs, collection-scoped duplicate product URLs, tracking parameters. The report treats all 18 statuses with the same visual weight, so a completely benign one sits right above the one that means you or an app you installed accidentally deindexed a product the client is actively selling.&lt;/p&gt;

&lt;p&gt;This is a reference for telling those apart during a build, a migration, or a routine client audit: what each status means per Google's own documentation, what produces it specifically on Shopify, and which ones are worth an incident, not just a shrug.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Page indexing report actually counting?
&lt;/h2&gt;

&lt;p&gt;Every status below is quoted from &lt;a href="https://support.google.com/webmasters/answer/7440203" rel="noopener noreferrer"&gt;Google's documentation for the Page indexing report&lt;/a&gt;, read on August 15, 2026. Google updates this list over time, so treat these as current as of that date, not permanent. Three things about the report trip up devs who are new to it.&lt;/p&gt;

&lt;p&gt;It's URL-level, not product-level. One product can appear under several reasons at once: as &lt;code&gt;/products/handle&lt;/code&gt;, as &lt;code&gt;/collections/summer/products/handle&lt;/code&gt;, as &lt;code&gt;/products/handle?variant=123&lt;/code&gt;. Counting rows and comparing that number to the product count in the catalog tells you nothing.&lt;/p&gt;

&lt;p&gt;It's an aggregate that lags. The report groups URLs by the state Google last recorded, which can be stale. A page can be fixed, recrawled, and reindexed while the report still shows the old grouping. A merchant on the Shopify Community ran into exactly this: they inspected a URL listed under "Crawled - currently not indexed" and the inspection said the page was indexed, prompting a &lt;a href="https://community.shopify.com/t/google-search-console-crawled-not-currently-indexed-but-page-is/346736" rel="noopener noreferrer"&gt;confused thread in August 2024&lt;/a&gt;. Both tools were right about different points in time.&lt;/p&gt;

&lt;p&gt;URL Inspection isn't live either, unless you ask it to be. Google states plainly that an inspection result is "from most recently indexed version of a page, not the live version on the web," and that pressing &lt;strong&gt;Test live URL&lt;/strong&gt; is what makes it "a live test: the tool fetches and examines the URL in real time" (&lt;a href="https://support.google.com/webmasters/answer/9012289" rel="noopener noreferrer"&gt;URL Inspection tool docs&lt;/a&gt;). When the report, the inspection, and the live test disagree, treat that as three timestamps, not three opinions, and trust the live test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which statuses are normal on a Shopify build (and when they're not)?
&lt;/h2&gt;

&lt;p&gt;These five account for most of a typical store's "Not indexed" count. Each still has an exception worth checking during QA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternate page with proper canonical tag.&lt;/strong&gt; Google: "This page is marked as an alternate of another page… This page correctly points to the canonical page, which is indexed." On Shopify, this is mostly &lt;code&gt;?variant=&lt;/code&gt; URLs and collection-scoped product URLs pointing back at the clean &lt;code&gt;/products/handle&lt;/code&gt;. The duplicate-URL system working as intended. Flag it if the canonical target listed is a &lt;em&gt;different&lt;/em&gt; product, or the store's &lt;code&gt;.myshopify.com&lt;/code&gt; domain instead of the storefront domain, since that means the canonical tag itself is wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Page with redirect.&lt;/strong&gt; Google: "This is a non-canonical URL that redirects to another page." Expected after handle renames, seasonal URL cleanups, and merges. Worth inspecting if a product URL redirects to the homepage or a collection instead of a replacement product; that pattern also silently drops the &lt;a href="https://storecanary.io/blog/shopify-product-page-redirect-merchant-center" rel="noopener noreferrer"&gt;linked Merchant Center listing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Duplicate without user-selected canonical.&lt;/strong&gt; Google: "This page is a duplicate of another page, although it doesn't indicate a preferred canonical page. Google has chosen the other page as the canonical for this page, and so will not serve this page in Search." Usually parameter URLs from apps and recommendation widgets. Ilana Davis's writeup on &lt;a href="https://www.ilanadavis.com/blogs/articles/when-to-ignore-search-console-indexing-issues-for-shopify-stores" rel="noopener noreferrer"&gt;when to ignore Search Console indexing issues on Shopify&lt;/a&gt; calls out URLs containing &lt;code&gt;wpm&lt;/code&gt; or &lt;code&gt;web-pixel-shopify-custom-pixel&lt;/code&gt;, plus &lt;code&gt;?pr_prod_strat=&lt;/code&gt; parameters, as safe to ignore. Filter those out before you look at anything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL blocked by robots.txt.&lt;/strong&gt; Google: "This page was blocked by your site's robots.txt file." Shopify's default &lt;code&gt;robots.txt.liquid&lt;/code&gt; already blocks &lt;code&gt;/admin&lt;/code&gt;, &lt;code&gt;/cart&lt;/code&gt;, &lt;code&gt;/checkout&lt;/code&gt;, &lt;code&gt;/search&lt;/code&gt;, &lt;code&gt;/policies/&lt;/code&gt;, and filtered collection URLs matching &lt;code&gt;/collections/*+*&lt;/code&gt;, per &lt;a href="https://help.shopify.com/en/manual/promoting-marketing/seo/editing-robots-txt" rel="noopener noreferrer"&gt;Shopify's robots.txt documentation&lt;/a&gt;. Those entries are correct. The exception that matters most: a &lt;code&gt;/products/&lt;/code&gt; URL showing up under this status means someone added a rule that shouldn't be there. Shopify itself warns that "incorrect use of the feature can result in loss of all traffic," and since the file lives in the theme, a theme update or a careless edit can reintroduce a bad &lt;code&gt;Disallow&lt;/code&gt; line without anyone noticing. &lt;a href="https://storecanary.io/blog/shopify-products-blocked-by-robots-txt" rel="noopener noreferrer"&gt;Full mechanism here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discovered - currently not indexed.&lt;/strong&gt; Google: "The page was found by Google, but not crawled yet. Typically, Google wanted to crawl the URL but this was expected to overload the site; therefore Google rescheduled the crawl." Common on a new store or right after a bulk catalog import; it's a queue, not a verdict. Worth a second look if product URLs sit here for weeks while the rest of the catalog crawls normally, since that usually means Googlebot's crawl budget is being burned on parameter URLs instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which statuses mean you shipped a bug that's hiding a live product?
&lt;/h2&gt;

&lt;p&gt;Anything below, on a URL for a product the client is actively selling, deserves a same-day fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL marked 'noindex'.&lt;/strong&gt; Google: "When Google tried to index the page it encountered a 'noindex' directive and therefore did not index it." This is the most consequential status for a Shopify build, because nothing in Shopify admin surfaces it: the product stays active, priced, and purchasable while being invisible to Google. Two known ways it gets applied: a theme conditional wrapping&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"robots"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"noindex"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and a &lt;code&gt;seo.hidden&lt;/code&gt; custom metafield, which &lt;a href="https://help.shopify.com/en/manual/promoting-marketing/seo/hide-a-page-from-search-engines" rel="noopener noreferrer"&gt;Shopify documents&lt;/a&gt; as applying to "products, pages, or blog posts." Both survive a CSV import, and both can be set by an app without a visible admin toggle. Across full-catalog scans of more than 90,000 Shopify product pages, a stray &lt;code&gt;noindex&lt;/code&gt; on live products is one of the most common critical findings. &lt;a href="https://storecanary.io/blog/shopify-phantom-noindex-google" rel="noopener noreferrer"&gt;Full breakdown here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not found (404).&lt;/strong&gt; Google: "This page returned a 404 error when requested." Correct and expected for a genuinely discontinued product. Worth checking in two cases: a handle that was renamed rather than retired (Shopify doesn't auto-redirect on a handle change), and a product deleted while it still had rankings and inbound links, since Shopify creates no redirect on deletion. &lt;a href="https://storecanary.io/blog/shopify-deleted-product-still-on-google" rel="noopener noreferrer"&gt;More on that here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Soft 404.&lt;/strong&gt; Google: "The page request returns what we think is a soft 404 response. This means that it returns a user-friendly 'not found' message but not a 404 HTTP response code." Usually a theme or app rendering an "unavailable" or empty state on a URL that still returns HTTP 200. Worth grepping your theme's product template for conditionals that swap in an empty state without changing the status code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server error (5xx).&lt;/strong&gt; Google: "Your server returned a 500-level error when the page was requested." Rare on Shopify's own infrastructure; at volume, this usually traces to an app proxy or a third-party endpoint the theme calls during server-side rendering. Check whether the errors cluster around an app's install date.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redirect error.&lt;/strong&gt; Covers "a redirect chain that was too long, a redirect loop, a redirect URL that eventually exceeded the max URL length, a bad or empty URL in the redirect chain." On a storefront, loops are usually two systems each trying to own the same redirect: a URL redirect rule set in Shopify admin pointing one way, and an app or locale rule pointing back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocked due to unauthorized request (401).&lt;/strong&gt; Google: "The page was blocked to Googlebot by a request for authorization." Usually points at a staging or preview environment, or an app proxy path sitting behind auth, rather than the live storefront. Note that a password-protected store is a separate mechanism: while the password page is active, Shopify states "search engines find and display only the password page," and other pages "are hidden and aren't displayed in search results" (&lt;a href="https://help.shopify.com/en/manual/online-store/themes/password-page" rel="noopener noreferrer"&gt;Shopify docs&lt;/a&gt;). Worth double-checking a client didn't leave store password protection on after launch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocked due to access forbidden (403).&lt;/strong&gt; Google: "HTTP 403 means that the user agent provided credentials, but was not granted access." On a storefront, this usually means a bot-management or firewall layer applied at the domain level is filtering Googlebot's requests, not a Shopify setting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL blocked due to other 4xx issue.&lt;/strong&gt; Google's catch-all for "a 4xx error not covered by any other issue type." Rare enough that it's worth inspecting the specific URL individually rather than pattern-matching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Crawled - currently not indexed.&lt;/strong&gt; Google's full description: "The page was crawled by Google but not indexed." Nothing blocked it; Googlebot read the page and declined to index it. This one alarms clients more than any other status, and it's mostly noise on Shopify since the URLs listed are usually duplicate surfaces. The work is in filtering: see the &lt;a href="https://storecanary.io/blog/crawled-currently-not-indexed-shopify" rel="noopener noreferrer"&gt;full breakdown of this status on Shopify products&lt;/a&gt; for how to tell a duplicate from a real failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Duplicate, Google chose different canonical than user.&lt;/strong&gt; Google: "This page is marked as canonical for a set of pages, but Google thinks another URL makes a better canonical." On Shopify this is structural: Shopify's own Liquid docs warn that "a standard product page and a product page in the context of a collection have the same content on separate URLs," and tell theme developers to consider the SEO implications of the &lt;a href="https://shopify.dev/docs/api/liquid/filters/within" rel="noopener noreferrer"&gt;&lt;code&gt;within&lt;/code&gt; filter&lt;/a&gt;. If most of the theme's internal links point at the collection-scoped URL, Google follows that internal linking pattern over your canonical tag. &lt;a href="https://storecanary.io/blog/shopify-google-chose-different-canonical" rel="noopener noreferrer"&gt;More detail here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do the two "indexed anyway" warnings mean?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Indexed, though blocked by robots.txt.&lt;/strong&gt; Google: "The page was indexed despite being blocked by your website's robots.txt file. Google always respects robots.txt, but this doesn't necessarily prevent indexing if someone else links to your page." The practical result is a search result Google can't describe, since it never read the page. If the blocked URL is a product, unblock it; don't add a &lt;code&gt;noindex&lt;/code&gt; tag, since Googlebot is never allowed to fetch the page to see it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Page indexed without content.&lt;/strong&gt; Google: "This page appears in the Google index, but for some reason Google could not read the content. Possible reasons are that the page might be cloaked to Google or the page might be in a format that Google can't index." Rare, and serious on a product page: the URL sits in the index carrying nothing to rank with. Run a live test to see what Googlebot actually receives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Page is indexed.&lt;/strong&gt; The clean pass, and worth saying plainly: it says nothing about ranking, price display, review stars, or whether the matching Merchant Center listing is approved. Those are decided elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you audit a client's Page indexing report step by step?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the reason, not the total.&lt;/strong&gt; Open Indexing → Pages and ignore the headline count. The reasons listed underneath are where noise and real failures diverge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter each reason down to product URLs.&lt;/strong&gt; Look only for URLs containing &lt;code&gt;/products/&lt;/code&gt;. Discard anything carrying &lt;code&gt;wpm&lt;/code&gt;, &lt;code&gt;web-pixel-shopify-custom-pixel&lt;/code&gt;, &lt;code&gt;?variant=&lt;/code&gt;, or &lt;code&gt;?pr_prod_strat=&lt;/code&gt;, and discard &lt;code&gt;/cart&lt;/code&gt;, &lt;code&gt;/checkout&lt;/code&gt;, &lt;code&gt;/search&lt;/code&gt;, and &lt;code&gt;/policies/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the survivors against the live catalog.&lt;/strong&gt; For every product URL still standing, confirm in Shopify admin whether it's active and for sale. A discontinued product under 404 is fine. A product the client sold this morning is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect one affected URL, then test it live.&lt;/strong&gt; Run URL Inspection for the indexed state and Google-selected canonical, then press Test live URL. If they disagree, your fix already landed and the report just hasn't caught up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match the status to its cause, fix the mechanism, then validate.&lt;/strong&gt; Use the entries above to trace the label back to what produced it (a &lt;code&gt;noindex&lt;/code&gt; directive, a &lt;code&gt;Disallow&lt;/code&gt; rule, a dead handle, a collection-scoped duplicate), fix that, then use Validate fix so Google rechecks the group instead of waiting on a natural recrawl.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why does this need to be a recurring check, not a one-time audit?
&lt;/h2&gt;

&lt;p&gt;Every status above describes a state Google recorded in the past, on URLs it happened to look at. Nothing in this report fires on the event that caused the change. A theme update that reintroduces a &lt;code&gt;Disallow&lt;/code&gt; line, an app that writes a &lt;code&gt;seo.hidden&lt;/code&gt; metafield across a collection during a bulk edit, a handle rename during a catalog migration: each of these is routine work, invisible in Shopify admin, and shows up here days or weeks later as one row among thousands.&lt;/p&gt;

&lt;p&gt;Running through this checklist once after launch is worth doing. But the report looks different next month for reasons nobody logged in a changelog, and the stores that get hurt aren't the ones that never checked; they're the ones that checked in March and had no reason to check again in July. Across full-catalog scans of more than 90,000 Shopify product pages, roughly 46% of stores carry at least one critical Google visibility issue, and roughly 73% carry at least one critical or warning-level issue. Worth baking this into whatever QA or handoff checklist you run for clients.&lt;/p&gt;

&lt;p&gt;How do you currently catch this kind of regression on client stores: manual Search Console checks, a cron job, something else? Curious what other devs and agencies have wired up.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article — with screenshots and ongoing updates — lives on &lt;a href="https://storecanary.io/blog/search-console-page-indexing-shopify" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Shopify zero-price bug that breaks Merchant Center feeds</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 24 Aug 2026 11:56:06 +0000</pubDate>
      <link>https://dev.to/max_buildogs/the-shopify-zero-price-bug-that-breaks-merchant-center-feeds-3abb</link>
      <guid>https://dev.to/max_buildogs/the-shopify-zero-price-bug-that-breaks-merchant-center-feeds-3abb</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Merchant Center's "Invalid value for category [price]" fires whenever the price attribute Google receives is exactly 0; the only documented exception is mobile devices sold under a contract promotion.&lt;/li&gt;
&lt;li&gt;On Shopify the zero comes from one of two unrelated causes: an unfilled variant price (a data issue, no code involved) or a theme's JSON-LD snippet reading a variable that can resolve to empty (a code bug that hits every product page).&lt;/li&gt;
&lt;li&gt;Scanning structured data across 90,000+ Shopify product pages, roughly 1 in 5 stores had at least one page emitting a zero price, usually on a small slice of the catalog, which is exactly why spot-checking a few products rarely catches it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a client's Shopify product silently drops out of Google Shopping, or picks up a "Invalid value for category [price]" disapproval in Merchant Center, don't start by re-checking the price field in admin. Start with the structured data the page actually serves. Google flags this error when the price attribute it receives is 0, a value it never treats as a real offer. On Shopify that zero rarely shows up where you'd expect it: it's usually buried in one variant nobody filled in, or it's a theme snippet reading a variable that can resolve to empty even when the admin price is correct. Both produce the identical Merchant Center error, and only one of them needs a code fix.&lt;/p&gt;

&lt;p&gt;This post covers how to tell which cause you're dealing with on a client's store, and how to fix each.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does "Invalid value for category [price]" actually mean?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.google.com/merchants/answer/12471484" rel="noopener noreferrer"&gt;Google's own documentation&lt;/a&gt; is blunt about it: the issue "occurs when you've entered 0 for the price [price] attribute." There's exactly one exception, for a mobile device sold as part of a contract or promotion, where a price attribute of 0 is allowed. Outside that case, a price of zero isn't a valid commercial offer, so Google rejects the item outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this bug hide from spot-checks?
&lt;/h2&gt;

&lt;p&gt;It's a loud failure with a quiet distribution. It rarely touches a whole catalog; it usually touches a handful of products inside a catalog that otherwise looks fine. Across a scan of more than 90,000 Shopify product pages, roughly one store in five had at least one page declaring a price of zero in its structured data, and in the typical case the affected pages were a small fraction of that store's catalog.&lt;/p&gt;

&lt;p&gt;That combination, widespread across stores but nearly invisible within any single one, is why it survives. Opening a few products in admin to eyeball the price almost never lands on the broken one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 1: is the variant price actually empty?
&lt;/h2&gt;

&lt;p&gt;Someone created the product as a draft with a placeholder price and never filled it in, or added a variant later and left its price blank. Admin is telling the truth here, and the markup is faithfully reporting it. This is the mundane case: no code change needed, just set the price.&lt;/p&gt;

&lt;p&gt;The detail that catches people out is that it's usually one variant, not the product. A product whose first variant is priced correctly looks entirely normal at a glance, while a second or third variant sits at zero and drags the whole item into a disapproval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 2: is the theme reading the wrong price variable?
&lt;/h2&gt;

&lt;p&gt;Here admin is correct and the markup still says zero. The theme's JSON-LD snippet is reading a field that can be empty instead of the one that always resolves to the displayed variant. In Liquid, that means checking whether the snippet uses something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;"price": "&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;selected_or_first_available_variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;price&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;money_without_currency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than a raw &lt;code&gt;product.price&lt;/code&gt;, &lt;code&gt;product.variants.first.price&lt;/code&gt;, or any field that can be nil when a variant is out of stock or unpublished. &lt;code&gt;product.selected_or_first_available_variant&lt;/code&gt; is the one that reliably matches what's rendered on the page.&lt;/p&gt;

&lt;p&gt;This version is the one worth flagging in a code review, because it isn't a one-off data mistake, it's a snippet that runs on every product page on the store. It also survives every correction made in admin, which is what makes it confusing to debug from the client side: they fix the price, the price is right, and Google keeps rejecting the item anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you tell which one you're looking at?
&lt;/h2&gt;

&lt;p&gt;Run the product URL through the &lt;a href="https://search.google.com/test/rich-results" rel="noopener noreferrer"&gt;Rich Results Test&lt;/a&gt; and read the &lt;code&gt;price&lt;/code&gt; field on the detected Product. That's the number Google actually receives, not the one typed into admin.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the same product in &lt;strong&gt;Shopify Admin → Product → Pricing&lt;/strong&gt; and check every variant, not just the first.&lt;/li&gt;
&lt;li&gt;If a variant has no price, that's your answer: set it.&lt;/li&gt;
&lt;li&gt;If every variant price is correct in admin and the markup still shows 0, the bug is in the theme snippet: confirm it reads &lt;code&gt;product.selected_or_first_available_variant.price&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's the actual fix, and how do you confirm it?
&lt;/h2&gt;

&lt;p&gt;For the data case, filling in the missing variant price is the entire fix. For the code case, correcting the JSON-LD variable fixes every product on the store at once, so it's worth doing properly rather than patching one product's snippet.&lt;/p&gt;

&lt;p&gt;Either way, revalidate with the Rich Results Test before telling the client it's resolved, then let the Merchant Center feed refresh (or reupload manually). Affected items show up under the &lt;strong&gt;Needs attention&lt;/strong&gt; tab once Google has re-crawled the feed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you check an entire client catalog, not just one product?
&lt;/h2&gt;

&lt;p&gt;Merchant Center's Needs attention tab only lists products that already reached a feed. A product that never made it into a feed can carry a zero price in its structured data indefinitely without ever surfacing in that report. If you're auditing a client's store, the structured data actually served on live product pages is the source of truth, not the feed diagnostics. This is worth building into any theme handoff or Merchant Center audit checklist, since it's cheap to check and easy to miss.&lt;/p&gt;

&lt;p&gt;It's also worth distinguishing from two related failures: a &lt;a href="https://storecanary.io/blog/merchant-center-price-mismatch-shopify" rel="noopener noreferrer"&gt;price mismatch&lt;/a&gt;, where the page and the markup disagree on a real price, and a &lt;a href="https://storecanary.io/blog/google-wrong-price-shopify" rel="noopener noreferrer"&gt;stale price&lt;/a&gt;, where the price is valid but out of date. A zero price is neither: the page and the markup usually agree, on a number that can't be right.&lt;/p&gt;

&lt;p&gt;Have you run into this on a client's theme before, was it a missing variant price or a bad JSON-LD variable?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article — with screenshots and ongoing updates — lives on &lt;a href="https://storecanary.io/blog/invalid-value-category-price-shopify" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Shopify 'crawled not indexed': triage guide for agency devs</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Fri, 21 Aug 2026 11:58:37 +0000</pubDate>
      <link>https://dev.to/max_buildogs/shopify-crawled-not-indexed-triage-guide-for-agency-devs-57f7</link>
      <guid>https://dev.to/max_buildogs/shopify-crawled-not-indexed-triage-guide-for-agency-devs-57f7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most URLs in the "Crawled - currently not indexed" report on a Shopify store are expected duplicates (collection-scoped product URLs, &lt;code&gt;?variant=&lt;/code&gt; parameters); their absence from the index is consolidation, not a bug.&lt;/li&gt;
&lt;li&gt;A real &lt;code&gt;/products/handle&lt;/code&gt; URL that survives triage usually has one of three causes: a canonical tag pointing elsewhere, thin content shared across many stores, or broken Product structured data.&lt;/li&gt;
&lt;li&gt;The fix starts with filtering the export, not reading the raw count.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you open Search Console for a client's Shopify store and see dozens of product pages sitting under "Crawled - currently not indexed," the immediate question is: platform quirk or actual indexing failure? The distinction matters because Shopify's URL architecture generates legitimate duplicates by default, and most of what fills this report falls into that category.&lt;/p&gt;

&lt;p&gt;Google's own reference defines this status in one sentence: &lt;a href="https://support.google.com/webmasters/answer/7440203" rel="noopener noreferrer"&gt;"The page was crawled by Google but not indexed."&lt;/a&gt; Compare it with the neighboring statuses. "URL blocked by robots.txt" is an access refusal you can switch off. "URL marked 'noindex'" is an explicit directive you can remove. This one is a judgment about the page's content and signals, and there is no switch for it.&lt;/p&gt;

&lt;p&gt;This article gives you the triage process: which URL shapes to filter out immediately, the three root causes worth investigating on surviving URLs, and a five-step inspection checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this report fill up with URLs that aren't the problem?
&lt;/h2&gt;

&lt;p&gt;A Shopify storefront generates far more URLs than it has products. Before reading anything into the count, export the report and filter to URLs containing &lt;code&gt;/products/&lt;/code&gt;. Everything else is a separate question.&lt;/p&gt;

&lt;p&gt;Two shapes in particular tend to dominate this list, and both are duplicates by design:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collection-scoped product URLs.&lt;/strong&gt; Shopify serves the same product at &lt;code&gt;/products/handle&lt;/code&gt; and again at &lt;code&gt;/collections/name/products/handle&lt;/code&gt;. Shopify's own documentation for the &lt;a href="https://shopify.dev/docs/api/liquid/filters/within" rel="noopener noreferrer"&gt;&lt;code&gt;within&lt;/code&gt; filter&lt;/a&gt; says it directly: "Because a standard product page and a product page in the context of a collection have the same content on separate URLs, you should consider the SEO implications of using the within filter." When Google finds several URLs with identical content, it picks one canonical and leaves the others out. That is expected consolidation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Variant parameters.&lt;/strong&gt; A &lt;code&gt;?variant=12345&lt;/code&gt; URL is the same product page with a different option preselected. There is nothing distinct to index.&lt;/p&gt;

&lt;p&gt;If the entire list is these two shapes plus non-product URLs, the right action is none.&lt;/p&gt;

&lt;h2&gt;
  
  
  What causes a real &lt;code&gt;/products/&lt;/code&gt; URL to stay unindexed?
&lt;/h2&gt;

&lt;p&gt;Once you have filtered the list and real product URLs remain, there are three causes to check in order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the canonical point somewhere else?
&lt;/h3&gt;

&lt;p&gt;This is the one that looks like a content problem but is mechanical. If the page's &lt;code&gt;&amp;lt;link rel="canonical"&amp;gt;&lt;/code&gt; points to a URL other than itself, Google treats the page as a duplicate. Run the URL through URL Inspection in Search Console and compare the &lt;strong&gt;User-declared canonical&lt;/strong&gt; with the &lt;strong&gt;Google-selected canonical&lt;/strong&gt;. If they differ, that is your answer. The &lt;a href="https://storecanary.io/blog/shopify-google-chose-different-canonical" rel="noopener noreferrer"&gt;canonical article&lt;/a&gt; covers why the collection URL often wins this comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the page have unique content?
&lt;/h3&gt;

&lt;p&gt;A product page carrying a supplier description shared verbatim across hundreds of stores gives Google little reason to add another copy to the index. This rarely has a code fix; it is frequently the real answer on dropshipped or catalog-imported stores. Sizing detail, materials, genuine reviews, and shipping specifics are the content that changes the calculation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the structured data parsing correctly?
&lt;/h3&gt;

&lt;p&gt;Worth eliminating before concluding anything about content quality. If the &lt;code&gt;Product&lt;/code&gt; block has a syntax error, the whole declaration is invisible to Google, usually with no visible symptom on the page itself. The &lt;a href="https://storecanary.io/blog/unparsable-structured-data-shopify" rel="noopener noreferrer"&gt;unparsable structured data article&lt;/a&gt; shows how a single stray character silently discards an entire block.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you triage this in five minutes?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Search Console &amp;gt; Indexing &amp;gt; Pages&lt;/strong&gt;, click &lt;strong&gt;Crawled - currently not indexed&lt;/strong&gt;, and export the list.&lt;/li&gt;
&lt;li&gt;Filter to URLs containing &lt;code&gt;/products/&lt;/code&gt;, then remove anything containing &lt;code&gt;/collections/&lt;/code&gt; or &lt;code&gt;?variant=&lt;/code&gt;. What remains is the list that matters.&lt;/li&gt;
&lt;li&gt;Take one surviving URL and run &lt;strong&gt;URL Inspection&lt;/strong&gt; on it. Compare &lt;em&gt;User-declared canonical&lt;/em&gt; with &lt;em&gt;Google-selected canonical&lt;/em&gt;. If they differ, you have the cause.&lt;/li&gt;
&lt;li&gt;If the canonicals agree, open the page and assess what content exists there that does not exist anywhere else. If the honest answer is nothing, that is the finding.&lt;/li&gt;
&lt;li&gt;Run the same URL through the &lt;a href="https://search.google.com/test/rich-results" rel="noopener noreferrer"&gt;Rich Results Test&lt;/a&gt; to confirm the &lt;code&gt;Product&lt;/code&gt; structured data still parses.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The pattern behind most of these silent failures: the Shopify admin shows a healthy, active, priced product, while the public URL tells Google something different. This status is a per-URL decision, and that decision usually has a mechanical cause one layer below it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://storecanary.io/blog/search-console-page-indexing-shopify" rel="noopener noreferrer"&gt;Every status in the Page indexing report, explained for Shopify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://storecanary.io/blog/shopify-products-not-showing-on-google" rel="noopener noreferrer"&gt;11 reasons a Shopify product disappears from Google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://storecanary.io/blog/shopify-google-chose-different-canonical" rel="noopener noreferrer"&gt;User-declared canonical vs Google-selected on Shopify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://storecanary.io/blog/unparsable-structured-data-shopify" rel="noopener noreferrer"&gt;Unparsable structured data on Shopify: the fix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://storecanary.io/blog/shopify-phantom-noindex-google" rel="noopener noreferrer"&gt;The phantom noindex: how Shopify apps silently hide products from Google&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Have you hit this on a client store? What was the root cause when you dug in?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full version of this article - with screenshots and ongoing updates - lives on &lt;a href="https://storecanary.io/blog/crawled-currently-not-indexed-shopify" rel="noopener noreferrer"&gt;the StoreCanary blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>seo</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
