<?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: Arvio</title>
    <description>The latest articles on DEV Community by Arvio (@arvio).</description>
    <link>https://dev.to/arvio</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%2F4093045%2F1f6cad86-b3ce-49f5-8493-6b46ac75c184.png</url>
      <title>DEV Community: Arvio</title>
      <link>https://dev.to/arvio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arvio"/>
    <language>en</language>
    <item>
      <title>What It Looks Like When the Fix Is Already Written</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Mon, 21 Sep 2026 06:09:50 +0000</pubDate>
      <link>https://dev.to/arvio/what-it-looks-like-when-the-fix-is-already-written-2m9g</link>
      <guid>https://dev.to/arvio/what-it-looks-like-when-the-fix-is-already-written-2m9g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/arvio-writes-the-fix?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=arvio-writes-the-fix_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/arvio-writes-the-fix&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Other tools tell you what's wrong. This one has already written the fix — you just approve it.&lt;/strong&gt; This post is one recorded run on our own demo store, screen by screen, so you can see what that actually means before installing anything.&lt;/li&gt;
&lt;li&gt;The product we asked about had a &lt;strong&gt;62-character&lt;/strong&gt; description: &lt;code&gt;Solid acacia, finished by hand. Hand wash and dry immediately.&lt;/code&gt; What came back was a &lt;strong&gt;364-character&lt;/strong&gt; replacement, already drafted, sitting in an editable box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing was written until a button was pressed.&lt;/strong&gt; The confirmation card says, in those words: &lt;code&gt;Only this product will be updated — all others left untouched.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;After it ran, the result card carried &lt;code&gt;✓ Description updated&lt;/code&gt; and the line &lt;code&gt;This action can be undone. Let me know if you'd like to revert it.&lt;/code&gt; — we exercised the forward path only, so treat the undo as what the product offers, not as something this recording demonstrates.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The gap isn't knowing which products are thin. It's that fixing them is typing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See it on your own catalogue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio is an AI store operator: it reads your live catalogue, drafts the change, and shows it to you before anything is written.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=arvio-writes-the-fix&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=arvio-writes-the-fix_listing_top" rel="noopener noreferrer"&gt;See Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why a walkthrough instead of a feature list
&lt;/h2&gt;

&lt;p&gt;Most descriptions of this kind of tool stop at the noun. "It rewrites product descriptions" is true of a great many things, including a text box and some patience. What it doesn't tell you is the part that decides whether you'd actually use it: what happens between asking and the change landing, and how much of that you control.&lt;/p&gt;

&lt;p&gt;So this is a recording rather than a claim. One prompt, one product, on our own demo store. Everything quoted below is text that was on screen.&lt;/p&gt;

&lt;p&gt;The instruction we typed was deliberately narrow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rewrite the product description for one product only: Acacia Round Tray. Leave every other product untouched.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What was there before
&lt;/h2&gt;

&lt;p&gt;The product had a description. That's worth saying, because the interesting case isn't the empty field — it's the one that's technically filled and does no work:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Solid acacia, finished by hand. Hand wash and dry immediately.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sixty-two characters. It's accurate. It tells a shopper the material and how to wash it, and nothing about why the thing is worth buying — &lt;a href="https://arvio.a.xyz/blog/product-description-examples-shopify?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=arvio-writes-the-fix_blog2" rel="noopener noreferrer"&gt;what a description that does the work looks like&lt;/a&gt; is its own subject. Nothing in any admin flags this, because from the system's point of view the field is populated and the product is fine.&lt;/p&gt;

&lt;p&gt;This is the category of problem that survives every audit you run. A checker asks "is the description empty?" and gets a no.&lt;/p&gt;

&lt;h2&gt;
  
  
  What came back: a draft, not a diagnosis
&lt;/h2&gt;

&lt;p&gt;Here is the part that's difficult to convey without a picture. The response was not a report saying the description was thin, and not a suggestion that it be improved. It was the replacement text, already written, in a box you can edit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5phzze2hqmiatnx1pccb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5phzze2hqmiatnx1pccb.png" alt="The old description struck through, the rewritten 364-character version already drafted in an editable box, and the Confirm Update button that hasn't been pressed yet." width="798" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The old line is struck through. Below it, under &lt;code&gt;New Description (editable)&lt;/code&gt;, sits the full replacement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Crafted from solid acacia wood and finished by hand, this round tray brings warm, natural grain to any table. Its generous surface is perfect for serving drinks, breakfast in bed, or keeping everyday essentials tidy. Because every piece of acacia has its own unique grain, no two trays are exactly alike. To keep it looking its best, hand wash and dry immediately.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's 364 characters of plain text against the original 62 — the lengths our catalogue diff recorded for the stored field before and after this run.&lt;/p&gt;

&lt;p&gt;Two things about that box are worth more than the word count. It is &lt;strong&gt;editable&lt;/strong&gt; — the draft is a starting point, not a verdict. And the button next to it had not been pressed. At this moment in the recording, nothing in the store had changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The confirmation card is the actual feature
&lt;/h2&gt;

&lt;p&gt;What makes write access to your catalogue acceptable isn't the quality of the drafting. It's that you can see precisely what it is about to do, in narrow enough terms that you could refuse.&lt;/p&gt;

&lt;p&gt;The card states its scope in a sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Only this product will be updated — all others left untouched.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It names the product, &lt;code&gt;Acacia Round Tray&lt;/code&gt;, shows the current description and the proposed one side by side, and labels itself &lt;code&gt;(reversible)&lt;/code&gt;. The button reads &lt;code&gt;Confirm Update&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the inversion worth understanding. A tool that gives you a list of problems has moved the work to you and kept none of the risk. A tool that writes to your catalogue on its own has taken the work and handed you the risk. The card is the attempt at neither: the work is done, and the decision is still yours. We wrote up why that boundary is where it is, and where it comes from, &lt;a href="https://arvio.a.xyz/blog/why-fix-not-report?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=arvio-writes-the-fix_blog3" rel="noopener noreferrer"&gt;in a separate post on why this is a fix and not a report&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Every product in the catalogue, same shape&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One approval per change, and a diff you can read before you give it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=arvio-writes-the-fix&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=arvio-writes-the-fix_listing_mid" rel="noopener noreferrer"&gt;See Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  After the button: what it tells you it did
&lt;/h2&gt;

&lt;p&gt;Pressing &lt;code&gt;Confirm Update&lt;/code&gt; runs the change. That's what makes this two rounds rather than one: the first produced the plan and the draft, and nothing else; the second is the one that writes. The result card reports back:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwsytuij9hjr4dke54qh4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwsytuij9hjr4dke54qh4.png" alt="The result card: a green check reading Description updated, the old and new descriptions, and a note that the action can be undone." width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three things on that card matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It confirms the scope it promised.&lt;/strong&gt; &lt;code&gt;All other products left untouched.&lt;/code&gt; The claim made before the change is the claim repeated after it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It shows the diff, not a success message.&lt;/strong&gt; &lt;code&gt;✓ Description updated&lt;/code&gt;, then &lt;code&gt;Old description&lt;/code&gt; struck through above &lt;code&gt;New description&lt;/code&gt; in full. You can read what landed without opening another tab. There's a &lt;code&gt;View in Shopify Admin →&lt;/code&gt; button for when you want to check the source of truth rather than take a card's word for it — which you should, and which is the whole reason that button exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It offers the way back.&lt;/strong&gt; In those words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This action can be undone. Let me know if you'd like to revert it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reversibility is what makes approving the first one bearable. If the cost of a bad rewrite is "fifteen minutes and some annoyance" rather than "find the original text, which you no longer have," then you'll actually try it on a real product instead of a test one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this run does not tell you
&lt;/h2&gt;

&lt;p&gt;Everything above is a recording of our own product on our own store, so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One product, one run.&lt;/strong&gt; This is not a sample. It says nothing about consistency across a catalogue, or about what the second hundred rewrites look like.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We didn't measure whether the new copy sells better.&lt;/strong&gt; No traffic, no conversion, no rankings. The 62-to-364 figure is a length, and length is not quality. A longer description that says nothing is worse than a short one that says something true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's our own demo store&lt;/strong&gt;, stocked and described by us. Your catalogue is messier in ways ours isn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The recording is sped up.&lt;/strong&gt; The interface printed &lt;code&gt;39s&lt;/code&gt; and &lt;code&gt;22s&lt;/code&gt; against the two rounds; we're repeating what it displayed, not offering it as an independently timed benchmark. Either way, don't read the clip's length as wall time, and don't read any of this as real-time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judgment is still yours.&lt;/strong&gt; The tool can tell you a field is thin. It cannot tell you that the thin description was deliberate, that legal signed off on that exact wording, or that the product is being discontinued next week.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this is actually useful
&lt;/h2&gt;

&lt;p&gt;The pitch is narrow.&lt;/p&gt;

&lt;p&gt;It is useful when you have a catalogue where a meaningful number of products have descriptions like that 62-character one — present, accurate, doing no work — and the reason they're still that way is that fixing them is typing, not deciding. That's the specific bottleneck: not knowing which products are thin, but that the fix for each one is a small writing job, and there are as many of them as you have products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And that's the question this post can't answer for you.&lt;/strong&gt; We recorded one product. If you have four hundred, the honest read is that you'd be reading four hundred drafts and pressing the button four hundred times — approval is a real cost, and it scales with the catalogue exactly the way the typing did. What changes is the unit: reading something already written and deciding yes or no, rather than composing it from an empty box. Whether that trade is worth it at your catalogue size is a judgment we haven't measured and won't pretend to have.&lt;/p&gt;

&lt;p&gt;It is not useful when you already know the exact values you want in a column. A tag migration or a vendor rename is a spreadsheet operation. It's also not useful if every word needs legal sign-off, because then the approval queue is the bottleneck and moving work into it doesn't help.&lt;/p&gt;

&lt;p&gt;If you want the wider tour of what the thing does beyond this one interaction, &lt;a href="https://arvio.a.xyz/blog/what-arvio-actually-does?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=arvio-writes-the-fix_blog5" rel="noopener noreferrer"&gt;we wrote that up here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does it change anything before I approve it?&lt;/strong&gt;&lt;br&gt;
Not in this run. The confirmation card appeared with the draft in an editable box and nothing was written until &lt;code&gt;Confirm Update&lt;/code&gt; was pressed. That is the design: the card names its scope — &lt;code&gt;Only this product will be updated — all others left untouched.&lt;/code&gt; — before you decide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I edit what it wrote instead of accepting it?&lt;/strong&gt;&lt;br&gt;
Yes. The box is labelled &lt;code&gt;New Description (editable)&lt;/code&gt; and behaves like a text field. The draft is a starting point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I approve something and regret it?&lt;/strong&gt;&lt;br&gt;
The result card says &lt;code&gt;This action can be undone. Let me know if you'd like to revert it.&lt;/code&gt; We exercised the forward path in this recording and read the change back in the admin; we did not record a revert, so treat the undo as what the product offers rather than as something we demonstrated here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long did it take?&lt;/strong&gt;&lt;br&gt;
The interface displayed &lt;code&gt;39s&lt;/code&gt; and &lt;code&gt;22s&lt;/code&gt; against the two rounds. That's what it printed, not a number we timed ourselves. The clip is sped up for watching, so don't time it against the video, and don't read it as real-time — it's a job that runs, not a live edit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this a bulk editor?&lt;/strong&gt;&lt;br&gt;
No, and the distinction is the point. A bulk editor applies values you already decided to many rows at once. Here the missing piece isn't applying a decision, it's making one per product: what should this description say. That's why every change comes with a diff and an approval rather than a row count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it write the same way for every product?&lt;/strong&gt;&lt;br&gt;
We ran one product. We can't tell you how consistent it is across a catalogue, and anyone quoting you a consistency figure from a single run is guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I have hundreds of products. Do I have to approve every single one?&lt;/strong&gt;&lt;br&gt;
In this recording, one change meant one approval. We haven't recorded what a few hundred look like, so we can't tell you there's a batch path or promise you there isn't — we'd rather say we don't know than guess on your behalf. Read the scale question as open, and size the trade yourself before you commit an afternoon to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will better descriptions improve my traffic or sales?&lt;/strong&gt;&lt;br&gt;
We didn't measure that and this post doesn't claim it. What it establishes is the interaction shape: the change arrives written, scoped, and reversible.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Figures and quoted strings: one recorded run on our own demo store, 2026-09-21. Description lengths from the run's catalogue diff (62 → 364 characters stored). All on-screen text quoted verbatim from the recording. Last updated: 2026-09-21.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arvio: AI Store Operator — &lt;a href="https://apps.shopify.com/arvio?article_slug=arvio-writes-the-fix&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=arvio-writes-the-fix_listing_end" rel="noopener noreferrer"&gt;install it on the Shopify App Store&lt;/a&gt;.&lt;/strong&gt; It goes through every product in the catalogue, one approval at a time.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/arvio-writes-the-fix?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=arvio-writes-the-fix_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/arvio-writes-the-fix&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=arvio-writes-the-fix_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why We Ship the Fix Instead of the Report</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Mon, 21 Sep 2026 06:05:41 +0000</pubDate>
      <link>https://dev.to/arvio/why-we-ship-the-fix-instead-of-the-report-4me7</link>
      <guid>https://dev.to/arvio/why-we-ship-the-fix-instead-of-the-report-4me7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/why-fix-not-report?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=why-fix-not-report_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/why-fix-not-report&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Other tools tell you what's wrong. This one has already written the fix — you just approve it.&lt;/strong&gt; This post is the reasoning behind that sentence, and the argument against the two more obvious designs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A list of problems is not progress.&lt;/strong&gt; A scan that returns a few hundred issues has converted one unknown into a few hundred small writing jobs and moved all of them to you. The finding was never the hard part.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent automation is worse, for a specific reason:&lt;/strong&gt; it takes the work and hands you the risk. Catalogue copy is not a thing you want changed while you aren't looking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The approval is where the design lives.&lt;/strong&gt; The change arrives written and scoped — &lt;code&gt;Only this product will be updated — all others left untouched.&lt;/code&gt; — and still doesn't run until you press a button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What this post does not establish:&lt;/strong&gt; this is an argument about design, not a measurement. We have no data showing this workflow produces better catalogues than an audit tool does.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Finding the problem was never the expensive part. Deciding and typing was.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;See what "already written" looks like&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio is an AI store operator: it reads your live catalogue, drafts the change, and shows it to you before anything is written.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=why-fix-not-report&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=why-fix-not-report_listing_top" rel="noopener noreferrer"&gt;See Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The report problem
&lt;/h2&gt;

&lt;p&gt;Run any catalogue scan and you get a number. A few hundred products with thin descriptions. Some number with no product type — &lt;a href="https://arvio.a.xyz/blog/shopify-product-type-missing?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=why-fix-not-report_blog2" rel="noopener noreferrer"&gt;we ran exactly that count on a real catalogue&lt;/a&gt;. Some quantity of empty categories and absent SKUs.&lt;/p&gt;

&lt;p&gt;That number feels like progress, and for about a day it is. You didn't know, and now you do. The trouble starts the moment you try to act on it, because the report has performed a conversion that nobody names out loud: &lt;strong&gt;it turned one vague worry into several hundred specific small jobs, and assigned every one of them to you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And they are not jobs that a list helps with. If the finding is "this description is thin," the work isn't locating it — the report already did that. The work is deciding what the description should say instead, and typing it — once per product, all the way down the list. The report is complete and accurate and has not moved you a single product closer to done.&lt;/p&gt;

&lt;p&gt;This is the part we'd expect to strand people, and it's not a failure of the tool: a scan that returns the same count next month is working exactly as designed, on a problem it was never able to touch. (We're reasoning about where the work lands, not reporting what anyone did — we have no data on how often these get re-run or abandoned.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The asymmetry is the whole thing.&lt;/strong&gt; Finding is cheap and automatable. Deciding and typing is expensive and wasn't automatable — until recently, which is what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we didn't just automate it
&lt;/h2&gt;

&lt;p&gt;The obvious response is to close the loop: if the tool can find the thin description and can also write a better one, let it write and be done. No list, no queue, no backlog of anything.&lt;/p&gt;

&lt;p&gt;We didn't build that, and the reason isn't timidity about what the writing is worth.&lt;/p&gt;

&lt;p&gt;It's that &lt;strong&gt;catalogue copy is customer-facing, permanent by default, and worth different amounts per product.&lt;/strong&gt; A rewrite that's fine on a serving tray is a problem on the product where the wording was negotiated, or the one with a compliance constraint, or the one being discontinued next week. The tool cannot know any of that. It can see that a field is thin. It cannot see that the thin description was deliberate.&lt;/p&gt;

&lt;p&gt;So full automation doesn't remove the risk. It relocates it — to a place where you find out about it later, from a customer, or not at all. The economics are bad in an asymmetric way: the upside of an unattended rewrite is a slightly better description, and the downside is wrong text live on your store for an unknown number of days. You would not take that trade on a product that matters, which means you would not enable it, which means the feature is useless precisely where the catalogue is valuable.&lt;/p&gt;

&lt;p&gt;And there's a second cost that's easy to miss: &lt;strong&gt;a change you didn't see is a change you can't learn from.&lt;/strong&gt; If the tool quietly rewrote every thin description you had, you have no idea whether it's any good at it. The approval isn't only a safety mechanism. It's the only moment where you find out what the thing actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The third position
&lt;/h2&gt;

&lt;p&gt;Between "here's a list, good luck" and "I changed it, hope that's fine" there's a position that only became available once drafting got cheap:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do the work. Don't do the deciding.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Concretely, that means a change arrives in a particular shape. The draft is written — not suggested, not described, written, and sitting in an editable box. In the run we recorded, a product whose entire description read &lt;code&gt;Solid acacia, finished by hand. Hand wash and dry immediately.&lt;/code&gt; came back with a replacement already drafted: &lt;em&gt;"Crafted from solid acacia wood and finished by hand, this round tray brings warm, natural grain to any table…"&lt;/em&gt; — 62 characters of stored text replaced by 364, before anyone had agreed to anything. The scope is stated in narrow terms before you commit to it. In that run, the confirmation card says it in a sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Only this product will be updated — all others left untouched.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then nothing happens until you press the button. And afterwards, the result card carries the way back:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This action can be undone. Let me know if you'd like to revert it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Take any one of those away and the thing stops working. If it isn't written, you're back to a list. If the scope isn't stated, you can't agree to it without checking the whole catalogue yourself. If you can't refuse it, your judgment is out of the loop precisely where the tool has none. And if you can't undo it, you won't press the button the first time.&lt;/p&gt;

&lt;p&gt;If you want to see that sequence on screen rather than described, &lt;a href="https://arvio.a.xyz/blog/arvio-writes-the-fix?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=why-fix-not-report_blog3" rel="noopener noreferrer"&gt;we published the whole run, screen by screen&lt;/a&gt;. The wider tour of what the thing does is &lt;a href="https://arvio.a.xyz/blog/what-arvio-actually-does?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=why-fix-not-report_blog4" rel="noopener noreferrer"&gt;over here&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One approval per change, and a diff you can read first&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=why-fix-not-report&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=why-fix-not-report_listing_mid" rel="noopener noreferrer"&gt;See Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Three designs, side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Audit / report&lt;/th&gt;
&lt;th&gt;Silent automation&lt;/th&gt;
&lt;th&gt;Written, then approved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who finds the problem&lt;/td&gt;
&lt;td&gt;The tool&lt;/td&gt;
&lt;td&gt;The tool&lt;/td&gt;
&lt;td&gt;The tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who writes the fix&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;You&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The tool&lt;/td&gt;
&lt;td&gt;The tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who decides it ships&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nobody&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What you hold afterwards&lt;/td&gt;
&lt;td&gt;A list of jobs&lt;/td&gt;
&lt;td&gt;Changes you didn't individually read&lt;/td&gt;
&lt;td&gt;A change you read and accepted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost of a bad call&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Live wrong text, found later&lt;/td&gt;
&lt;td&gt;Reject it, or undo it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What you learn about the tool&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Whether it's any good&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is the one people don't anticipate. After ten approvals you have a real opinion about whether the drafting is worth having, because you read ten drafts. Neither of the other two columns gives you that at any point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the boundary belongs
&lt;/h2&gt;

&lt;p&gt;"Keep a human in the loop" is easy to say and usually means nothing, because it doesn't specify &lt;em&gt;where&lt;/em&gt;. Put the human too early and you've built an audit: they do the work. Put them too late and the decision has already been made for them; a log they can read afterwards is a different thing from a choice they got to make.&lt;/p&gt;

&lt;p&gt;The rule we landed on is about reversibility, and it's not specific to catalogues:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automate up to the last irreversible step. Stop there, and make that step cheap to refuse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drafting is reversible — a draft you don't like costs nothing, you just don't press the button. Writing to a live storefront is not reversible in the same way; there is an interval where the wrong thing is public. So the boundary goes between them. Not because writes are scary, but because that's the first point where a mistake acquires a duration.&lt;/p&gt;

&lt;p&gt;This also explains why the answer isn't "add a confidence threshold and auto-apply the easy ones." Confidence measures whether the model thinks the copy is good. It has no access to the thing that actually determines whether a change is safe — whether &lt;em&gt;this product&lt;/em&gt; matters, and why it currently reads the way it does. Those aren't the same question, and only one of them is yours to answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this argument doesn't prove
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's an argument, not a measurement.&lt;/strong&gt; We have no comparative data showing this workflow produces better catalogues than an audit tool does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approval is a real cost.&lt;/strong&gt; Per-change review does not scale to infinity, and if you have hundreds of thin descriptions you will be pressing a button a lot. We think it beats writing hundreds of descriptions. That's a judgment, not a finding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some jobs genuinely want the other designs.&lt;/strong&gt; If you already know the exact values you want in a column — a tag migration, a vendor rename — that's a spreadsheet operation, and a report plus a batch update is the right shape for it. If a field is truly low-stakes and uniform, automate it and don't look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We're describing two designs we didn't build, in their plainest form.&lt;/strong&gt; Real audit tools and real automation products carry mitigations we haven't credited here — previews, dry runs, staged rollouts, per-field rules. The comparison is between the three shapes, not a claim about any particular product's worst case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We're describing our own product's design&lt;/strong&gt;, so read this as reasoning you can check against the recording, not as a neutral survey.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isn't this just an audit tool with extra steps?&lt;/strong&gt;&lt;br&gt;
The opposite, in the one place it matters. An audit ends by handing you the writing. This ends by handing you a decision, with the writing already done. Same finding, and everything expensive after the finding lands in a different place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not let it run unattended on the obvious cases?&lt;/strong&gt;&lt;br&gt;
Because "obvious" is a property of the copy, and the risk is a property of the product. The tool can see that a description is thin. It can't see that the wording was negotiated, or that the item is being discontinued. Those don't correlate with how confident the model is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I already know exactly what I want in every row?&lt;/strong&gt;&lt;br&gt;
Then you don't have this problem, and you shouldn't reach for this. Knowing the value already means the only work left is applying it, which a spreadsheet operation does perfectly well. What this is for is the other case: where the decision doesn't exist yet, and somebody has to work out what this particular description should say.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doesn't approving every change defeat the point?&lt;/strong&gt;&lt;br&gt;
It would if approving were as expensive as writing. It isn't — reading something already written and deciding yes or no is a smaller job than composing it from nothing. That gap is where the saving lives, and it's real even though approval is not costless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I trust it after a hundred changes?&lt;/strong&gt;&lt;br&gt;
That's the right instinct and you'd have earned it by reading a hundred drafts, which is exactly the thing the other two designs never let you do. We're not claiming the approval step should be permanent for everyone forever — only that it's where you start, and that removing it should be a decision you make with evidence rather than a default you never saw.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do you have data showing this works better?&lt;/strong&gt;&lt;br&gt;
No. This post is a design argument. The measurable claims we do have are about the interaction — what appears on screen, in what order — and those are in &lt;a href="https://arvio.a.xyz/blog/arvio-writes-the-fix?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=why-fix-not-report_blog6" rel="noopener noreferrer"&gt;the walkthrough&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;On-screen strings quoted here are from one recorded run on our own demo store, 2026-09-21, reproduced with the full sequence in "What It Looks Like When the Fix Is Already Written". Last updated: 2026-09-21.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arvio: AI Store Operator — &lt;a href="https://apps.shopify.com/arvio?article_slug=why-fix-not-report&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=why-fix-not-report_listing_end" rel="noopener noreferrer"&gt;install it on the Shopify App Store&lt;/a&gt;.&lt;/strong&gt; It writes the change and waits for you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/why-fix-not-report?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=why-fix-not-report_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/why-fix-not-report&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=why-fix-not-report_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why ChatGPT Can't See Your Shopify Store — and Which Part Is Actually Missing</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Sat, 12 Sep 2026 18:58:23 +0000</pubDate>
      <link>https://dev.to/arvio/why-chatgpt-cant-see-your-shopify-store-and-which-part-is-actually-missing-km8</link>
      <guid>https://dev.to/arvio/why-chatgpt-cant-see-your-shopify-store-and-which-part-is-actually-missing-km8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/chatgpt-cant-see-shopify-store?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/chatgpt-cant-see-shopify-store&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start by asking an assistant three questions about your own store. &lt;strong&gt;The shape of the wrong answer tells you which check to run&lt;/strong&gt; — and the four checks below only mean anything in order.&lt;/li&gt;
&lt;li&gt;On our own demo store (&lt;code&gt;fashionmore-3397.myshopify.com&lt;/code&gt;, read 2026-09-13), &lt;strong&gt;every page-level check passed&lt;/strong&gt;: 4 of 4 product pages returned HTTP 200 with 322,364–371,301 bytes of HTML, and 4 of 4 carried a product node in their structured data.&lt;/li&gt;
&lt;li&gt;Same store, same day: &lt;strong&gt;8 of 67 products have a description under 120 characters, 19 have no product type, 9 have no tags&lt;/strong&gt; — while 0 have an empty description body, 0 are missing a vendor, and 0 are missing a SKU. One page publishes a 52-character description; two publish an &lt;strong&gt;empty&lt;/strong&gt; &lt;code&gt;category&lt;/code&gt; rather than none at all, which is why nothing anywhere reports an error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What this post does not establish:&lt;/strong&gt; we measured what an assistant would have to read, not what any assistant did. Nothing here shows that filling these fields changes how you get described or recommended.&lt;/li&gt;
&lt;li&gt;Across 42 other storefronts read 2026-08-31: vendor, price and stock status were present on &lt;strong&gt;100%&lt;/strong&gt; of 5,935 products and 31,767 variants, while &lt;strong&gt;35.8% of variants had no SKU&lt;/strong&gt;. What Shopify fills in is complete; what a person has to type is what's missing.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your store probably isn't invisible. It has nothing to say.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The four checks are cheap. Filling the fields is what stalls&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio is an AI store operator: it reads your live catalogue, finds the products that have nothing in them, and writes the missing description for you to approve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=chatgpt-cant-see-shopify-store&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_listing_top" rel="noopener noreferrer"&gt;See Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Start here: ask an assistant about your own store (two minutes, nothing to install)
&lt;/h2&gt;

&lt;p&gt;Everything else in this post is a technical check. This one isn't, it's faster, and it's the only step that tells you which technical check is worth your afternoon.&lt;/p&gt;

&lt;p&gt;Open a new chat with whichever assistant you care about and paste these three, one at a time, with your own domain in them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. What does yourstore.com sell? Be specific about the categories.
2. List five products from yourstore.com, with the price and whether each one is in stock.
3. I want &amp;lt;a thing you actually sell&amp;gt; for under &amp;lt;a price you actually charge&amp;gt;.
   Does yourstore.com have one? Link it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now read the &lt;em&gt;shape&lt;/em&gt; of what comes back, not whether it's flattering:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What comes back&lt;/th&gt;
&lt;th&gt;What that tells you&lt;/th&gt;
&lt;th&gt;Go to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"I can't find that site" / "I'm unable to browse"&lt;/td&gt;
&lt;td&gt;Nothing was read. Everything downstream is unanswerable until this changes&lt;/td&gt;
&lt;td&gt;Step 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A plausible description of your niche, but it can't name a single product&lt;/td&gt;
&lt;td&gt;Something was read, and there was nothing specific in it&lt;/td&gt;
&lt;td&gt;Step 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product names that are nearly right, or products you've never sold&lt;/td&gt;
&lt;td&gt;It read something thin and filled the gap itself&lt;/td&gt;
&lt;td&gt;Step 3, then 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right names, wrong prices, or things you discontinued&lt;/td&gt;
&lt;td&gt;It read you fine. What it read is stale&lt;/td&gt;
&lt;td&gt;Step 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right names, right details&lt;/td&gt;
&lt;td&gt;This isn't a data problem, and this post won't help you&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Before you believe the answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run a control.&lt;/strong&gt; In the same chat, ask the same three questions about a store you know is well stocked and well described — a competitor is ideal. If that one also comes back empty, you measured the assistant's browsing, not your catalogue. A blank answer has two completely different causes and they look identical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One run is a smoke test, not a measurement.&lt;/strong&gt; Answers move between assistants, between sessions, and depending on whether that chat can fetch pages at all. We are not reporting what any assistant said about our store, because we have no clean way to measure that; everything numbered in this post came from endpoints anyone can fetch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  First, the suspicion you can cross off: it probably isn't permission
&lt;/h2&gt;

&lt;p&gt;When a store can't be described by ChatGPT, the first guess is usually that something is blocking the crawler. It is worth thirty seconds to rule out, because it is the one part of this you can settle by reading a single file.&lt;/p&gt;

&lt;p&gt;An assistant that browses reads your storefront the way any other bot does: it requests the page, takes the HTML your server returns, and reads whatever structured data is sitting in it. It does not log in, and it does not see your admin. Whether it is allowed to make that request at all is decided by one file: &lt;code&gt;yourstore.com/robots.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So we read that file on all 42 storefronts in this post's sample, on 2026-09-13. All 42 responded. &lt;strong&gt;41 of the 42 carry no AI-crawler rule at all&lt;/strong&gt; — no &lt;code&gt;GPTBot&lt;/code&gt;, no &lt;code&gt;Google-Extended&lt;/code&gt;, no &lt;code&gt;PerplexityBot&lt;/code&gt;, in either direction. The single store that names them allows all three. &lt;strong&gt;Not one of the 42 blocks any of them.&lt;/strong&gt; Shopify does not ship a block by default, and almost nobody adds one.&lt;/p&gt;

&lt;p&gt;Check your own if you like: open &lt;code&gt;yourstore.com/robots.txt&lt;/code&gt; and search it for &lt;code&gt;GPTBot&lt;/code&gt;. Finding nothing means nothing is being blocked. If you do want to change what that file says to AI crawlers, &lt;a href="https://arvio.a.xyz/blog/shopify-gptbot?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_blog2" rel="noopener noreferrer"&gt;that is its own job and we wrote it up separately&lt;/a&gt;. The same goes for the newer machine-readable files stores publish for agents — &lt;a href="https://arvio.a.xyz/blog/agents-md-what-stores-actually-ship?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_blog3" rel="noopener noreferrer"&gt;we went and counted what is actually out there&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What none of this tells you is whether ChatGPT ever chose to fetch you, or what it did with the page once it had it. Permission is simply the cheapest thing to eliminate. Everything below assumes you were read, and asks a different question: what was there to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order is the entire point
&lt;/h2&gt;

&lt;p&gt;The checks themselves are not secret, and you have probably already met most of them. What gets left out is that &lt;strong&gt;three of the four are meaningless until the one above them passes&lt;/strong&gt;, and that a failure at check 3 looks, from the outside, exactly like a failure at check 1 — both end with an assistant that can't tell a shopper anything about you.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Where you look&lt;/th&gt;
&lt;th&gt;Passing looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Does the page come back at all?&lt;/td&gt;
&lt;td&gt;Your product URL in a private window&lt;/td&gt;
&lt;td&gt;A real page, not a shell or a redirect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Is there a product node in the markup?&lt;/td&gt;
&lt;td&gt;Any structured-data testing tool, fed the product URL&lt;/td&gt;
&lt;td&gt;It finds one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Are the fields inside it filled?&lt;/td&gt;
&lt;td&gt;The same readout — read the values, not the field names&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;description&lt;/code&gt; and &lt;code&gt;category&lt;/code&gt; have something in them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Is what it says still true?&lt;/td&gt;
&lt;td&gt;Three products the assistant named, against your admin&lt;/td&gt;
&lt;td&gt;Price, stock and title match&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our own store is the argument for starting at the top rather than in the middle: it passes 1 and 2 on every page we checked, and it still has products with nothing to say.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Does the page come back at all?
&lt;/h2&gt;

&lt;p&gt;We fetched four product pages from our demo store anonymously on 2026-09-13. All four returned &lt;strong&gt;HTTP 200&lt;/strong&gt;, with &lt;strong&gt;322,364 to 371,301 bytes&lt;/strong&gt; of HTML each. There's a whole page there.&lt;/p&gt;

&lt;p&gt;If your check fails here — a redirect loop, a password-protected storefront, a 404 on a handle you renamed — stop. Nothing below this line matters until the page responds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Is there a product node in the markup? (and why a zero here might be your fault)
&lt;/h2&gt;

&lt;p&gt;All four pages carried one. But &lt;strong&gt;three of the four published it as &lt;code&gt;ProductGroup&lt;/code&gt;, and only one as &lt;code&gt;Product&lt;/code&gt;&lt;/strong&gt; — the difference being whether the item has variants.&lt;/p&gt;

&lt;p&gt;That's worth knowing because it's the cheapest way to misdiagnose yourself. A tool or a search that only looks for &lt;code&gt;Product&lt;/code&gt; reports nothing found on three of those four pages, and "nothing found" reads exactly like "this store has no structured data." It isn't. It's the wrong query.&lt;/p&gt;

&lt;p&gt;So when step 2 returns a zero, run the same check against a store you're confident is set up properly. If that also returns zero, your check is wrong. (A tool that comes back with actual errors rather than with nothing at all is telling you about &lt;a href="https://arvio.a.xyz/blog/shopify-structured-data-errors?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_blog4" rel="noopener noreferrer"&gt;a different problem with a different fix&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Are the fields inside it filled? (this is the check our own store failed)
&lt;/h2&gt;

&lt;p&gt;Here's what those same four pages publish inside the product node:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Page&lt;/th&gt;
&lt;th&gt;Structured-data &lt;code&gt;description&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;&lt;code&gt;category&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small charcuterie board&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52 characters&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Serveware&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handled cake stand&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;77 characters&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Serveware&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Round serving tray&lt;/td&gt;
&lt;td&gt;362 characters&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;empty string&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Woven bowls&lt;/td&gt;
&lt;td&gt;784 characters&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;empty string&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;An empty string is not a missing field.&lt;/strong&gt; Those two pages don't omit &lt;code&gt;category&lt;/code&gt; — they publish it with nothing in it. A check that asks "is there a category?" gets a yes. This is the failure mode with no error message anywhere: the page is fine, the markup is there, the field is present, and the value is blank.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The blank is just your admin, passed through.&lt;/strong&gt; The two pages with an empty &lt;code&gt;category&lt;/code&gt; are the same two products that have no product type in the store's catalogue feed; the two with a value are ones that do. Four pages is not proof of how every theme builds that field, but it is the same two products, and it means the fix isn't a markup project — it's the product type field, on 19 products.&lt;/p&gt;

&lt;p&gt;Four pages is a spot check. To widen it to the whole catalogue we read the store's own public catalogue endpoint — &lt;code&gt;yourstore.com/products.json?limit=250&lt;/code&gt;, which any Shopify storefront that isn't password-protected serves without a login — and counted the fields product by product. That is the version of this check that scales past a handful of pages, and it runs on your store exactly as it ran on ours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to actually count it, without writing a line of code.&lt;/strong&gt; Open that URL in a browser. What comes back is a single unbroken line of text — on our 67-product store, about 160,000 characters of it — and you are not meant to read it. You are meant to search it. Press Cmd-F (Ctrl-F on Windows) and your browser's find bar will report how many matches it found. That number is your count.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Search for exactly this&lt;/th&gt;
&lt;th&gt;What it counts&lt;/th&gt;
&lt;th&gt;Our store&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"product_type":""&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Products with no product type&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"tags":[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Products with no tags&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"body_html":""&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Products with a completely empty description&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"vendor":""&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Products with no vendor&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;"sku":""&lt;/code&gt; and &lt;code&gt;"sku":null&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Variants with no SKU — add the two&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those are the same five numbers as the table below, which is the whole point: the count in this post is one you can reproduce against your own catalogue in about a minute.&lt;/p&gt;

&lt;p&gt;Three things to know before you trust it. If your browser shows a tidy collapsible tree rather than a wall of text, switch to its &lt;strong&gt;Raw Data&lt;/strong&gt; view first — the searches above are literal, and a reformatted view inserts spaces where the file has none. &lt;code&gt;limit=250&lt;/code&gt; is a ceiling, so a catalogue larger than 250 products is only partly counted this way. And the one row you cannot get from a search is the first one in the table below: "description under 120 characters" needs something that can measure length, not merely find a blank.&lt;/p&gt;

&lt;p&gt;All 67 products in that store, read the same day:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Products failing&lt;/th&gt;
&lt;th&gt;Products passing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Description under 120 plain-text characters&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No product type set&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No tags&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description completely empty&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No vendor&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No SKU on any variant&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bottom three rows are not a disclaimer, they're half the finding. Three of the six checks come back perfect. Nobody's store is uniformly empty, and if you go looking for "our data is a mess" you'll find yourself a contradiction and stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Is what it says still true?
&lt;/h2&gt;

&lt;p&gt;Step 3's failure is silence. Step 4's failure is worse, because it produces confident answers.&lt;/p&gt;

&lt;p&gt;In the 42 storefronts we read on 2026-08-31, &lt;strong&gt;1,718 of 31,767 variants — 5.4% — were marked out of stock and still published in the public catalogue&lt;/strong&gt;, across 34 of the 42 stores. &lt;a href="https://arvio.a.xyz/blog/shopify-sold-out-products-still-showing?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_blog5" rel="noopener noreferrer"&gt;Keeping a sold-out product listed is usually deliberate&lt;/a&gt; — a restock is coming, the page has history, returning customers search for it — so that is a count of a situation, not a count of mistakes. The stock flag itself was present on 100% of those variants, so nothing is hidden: an assistant that reads it can tell. An assistant that reads the title and the description and skips the flag will hand a shopper something you can't ship.&lt;/p&gt;

&lt;p&gt;Concretely: take three products the assistant named at the top of this post and check them against your admin. Right price? Right stock? Is that seasonal bundle from last year still live? This is the branch where "AI can see my store" is true and still costing you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your evening, in order
&lt;/h2&gt;

&lt;p&gt;If you do only what is on this list, you will have done the whole diagnosis. The times are what each step takes to run — not what it takes to fix whatever it turns up.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Do this&lt;/th&gt;
&lt;th&gt;How long&lt;/th&gt;
&lt;th&gt;What you end up knowing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Ask an assistant the three questions at the top of this post, with your own domain in them&lt;/td&gt;
&lt;td&gt;2 min&lt;/td&gt;
&lt;td&gt;Which row of that table you are in — and therefore which step below is the one that matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Ask the same three about a competitor you know is well stocked, &lt;strong&gt;in the same chat&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;2 min&lt;/td&gt;
&lt;td&gt;Whether an empty answer was about your store or about that assistant's browsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Open one of your own product URLs in a private window&lt;/td&gt;
&lt;td&gt;1 min&lt;/td&gt;
&lt;td&gt;Whether there is a real page there at all. If there is not, stop — nothing below this counts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Open &lt;code&gt;yourstore.com/robots.txt&lt;/code&gt; and search it for &lt;code&gt;GPTBot&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;30 sec&lt;/td&gt;
&lt;td&gt;Whether you are blocking anything. On the evidence above, you almost certainly are not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Feed that same product URL to a structured-data testing tool&lt;/td&gt;
&lt;td&gt;3 min&lt;/td&gt;
&lt;td&gt;Whether there is a product node. Accept &lt;code&gt;ProductGroup&lt;/code&gt; as well as &lt;code&gt;Product&lt;/code&gt; — most variant products publish the first one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;In that same readout, read the &lt;strong&gt;values&lt;/strong&gt; of &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;category&lt;/code&gt;, not the field names&lt;/td&gt;
&lt;td&gt;2 min&lt;/td&gt;
&lt;td&gt;Whether your fields are filled or merely present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Open &lt;code&gt;yourstore.com/products.json?limit=250&lt;/code&gt; and run the five searches from Step 3&lt;/td&gt;
&lt;td&gt;2 min&lt;/td&gt;
&lt;td&gt;Those same counts across your entire catalogue, instead of the one page you spot-checked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Take three products the assistant named back in step 1 and check price, stock and title against your admin&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;Whether what it read about you is still true&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Under twenty minutes, and you come out the other side with a number — how many of your products have nothing in them — instead of a feeling. What to do about that number is the last section of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the same checks look like on other people's stores
&lt;/h2&gt;

&lt;p&gt;We read the public catalogue of 42 storefronts drawn from a public list on 2026-08-31 — 5,935 products, 31,767 variants:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Present&lt;/th&gt;
&lt;th&gt;Missing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vendor&lt;/td&gt;
&lt;td&gt;5,935 / 5,935 — &lt;strong&gt;100%&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;31,767 / 31,767 — &lt;strong&gt;100%&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stock status&lt;/td&gt;
&lt;td&gt;31,767 / 31,767 — &lt;strong&gt;100%&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SKU&lt;/td&gt;
&lt;td&gt;20,381 / 31,767 — 64.2%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11,386 — 35.8%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And per store, because a single average hides the shape:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Share of variants with a SKU&lt;/th&gt;
&lt;th&gt;Stores&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All of them&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50–99%&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1–49%&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None at all&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So "merchants leave their data empty" is not a true statement, and if that's the premise you're working from you'll spend a week sweeping fields that were never broken. &lt;strong&gt;The fields Shopify fills in for you are complete in every store we read. The fields a human has to type are missing at scale.&lt;/strong&gt; Be careful how much weight you put on those three 100% rows: we never saw one of them fail, on any store, which means they may be describing how this endpoint serialises a product rather than how carefully anyone filled it in. The row that actually discriminates between stores is SKU. Price is there because you can't publish without it. Vendor is there because it's one box at the top of the form. SKU is missing on a third of variants because it's one box per variant, several hundred times, and nothing ever breaks when you skip it.&lt;/p&gt;

&lt;p&gt;That's the same reason our own store has 19 products with no product type. It isn't a knowledge problem — the merchant knows what a product type is. The work is one small decision per product, several hundred times over in a catalogue of any size, and no part of the system ever complains.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we measured, and what we could not
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The single store.&lt;/strong&gt; &lt;code&gt;fashionmore-3397.myshopify.com&lt;/code&gt; is our own demo store, so treat it as one named case; it is not a sample of anything. All 67 products read from its public catalogue on 2026-09-13; structured data read on 4 of those 67 product pages, not all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 42 stores.&lt;/strong&gt; Drawn from a public list: Shopify Community's Store Feedback board, where merchants post their own URL asking for a critique — 720 posts, 474 candidate domains, 230 still trading, from which 60 were drawn at random with a recorded seed — the other 244 had gone dark, so this frame is of survivors, and of merchants who post their store for critique, which skews new and small. We were only able to probe 43 of those 60, and all 43 had also published a machine-readable capability file, which makes them a tidier-than-average subset chosen by outcome rather than the random 60. &lt;strong&gt;So treat 35.8% as a floor, not an estimate&lt;/strong&gt;, and not as a figure representative of Shopify at large. 42 of the 43 returned a readable catalogue; one failed to resolve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Truncation.&lt;/strong&gt; We read the first 250 products per store. 16 of the 42 hit that ceiling, so their deeper catalogue isn't in these totals, and we don't know which way that pushes the SKU number. Median catalogue as read: 140.5 products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not measured: image alt text.&lt;/strong&gt; The catalogue endpoint doesn't expose that field at all, so we have no coverage figure for it in either direction — it's a blank, not a zero. Same for the SEO title tag. Both matter to an assistant reading your store; we just can't count them this way.&lt;/p&gt;

&lt;p&gt;We sell an agent that fills these fields in. And you can't recheck the multi-store numbers: the frame file, the list of 60 and the 42 domains are not published, so treat every figure above that isn't about our own store as ours rather than as something you verified. What you can reproduce is the method on your own store — ask it the three questions, run the four checks in order.&lt;/p&gt;

&lt;h2&gt;
  
  
  So the diagnosis is cheap and the fix is a few hundred repetitions
&lt;/h2&gt;

&lt;p&gt;That's the actual shape of this problem, and it's why the advice you've already read didn't help. Three ways through it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the admin, one product at a time.&lt;/strong&gt; The right call when the answer is different for every product, which &lt;a href="https://arvio.a.xyz/blog/shopify-product-page-optimization?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_blog6" rel="noopener noreferrer"&gt;for descriptions&lt;/a&gt; it genuinely is. It's also the option whose cost is entirely your evenings — at 67 products it's an afternoon; at several hundred it's the thing you've been meaning to do since spring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSV export and re-import.&lt;/strong&gt; Right when you already know the value you want in a column — supplier product types, &lt;a href="https://arvio.a.xyz/blog/shopify-tags-cleanup?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_blog7" rel="noopener noreferrer"&gt;a tag scheme you can derive from the handle&lt;/a&gt;, SKUs you can generate. Wrong for descriptions, because a spreadsheet doesn't know anything about the product, and several hundred near-identical descriptions clear every check in this post while helping nobody. Before you trust a round trip: export one collection, change exactly one product, re-import, and diff it against what you exported.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Something that goes through the whole catalogue for you.&lt;/strong&gt; That's the shape Arvio takes. Arvio is an AI store operator. It scans your store nightly, ranks what it finds — using your own store's data, which a public-catalogue read like this one can't see — then writes the edit against your live catalogue and holds it for your approval. Nothing ships until you approve it, and any edit can be undone in one click.&lt;/p&gt;

&lt;p&gt;The list was never the hard part. You could have generated it from that one URL in the first two minutes of this post, for nothing. An audit tool that hands you a list of problem pages has not written a single description.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The same fields, checked nightly, with the edit already written&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything in this article was read from public endpoints. Arvio reads your own store instead, ranks what it finds, drafts each change against your real catalogue and holds it until you approve. Any edit can be undone in one click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=chatgpt-cant-see-shopify-store&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_listing_mid" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  When this isn't your problem
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your catalogue is small.&lt;/strong&gt; The median store we read had 140.5 products; if you have thirty, an afternoon in the admin beats every tool on this page and you'll write better copy than any of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You fill every field as you add the product.&lt;/strong&gt; Then steps 1–4 all pass, your question is discovery or demand rather than data, and this post measured neither.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You already know the exact values you want in a column.&lt;/strong&gt; A tag migration, a vendor rename, a price change — that's a spreadsheet operation, and a bulk editor is the right tool for it. That isn't what Arvio is for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every word needs legal or clinical sign-off.&lt;/strong&gt; An approval queue that fills up nightly just moves the bottleneck.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why can't ChatGPT see my Shopify store?&lt;/strong&gt;&lt;br&gt;
There are at least four different reasons, and they produce nearly identical symptoms: the page doesn't come back, there's no product node in it, the node is there with empty fields, or everything is readable but out of date. Ask it the three questions at the top of this post and the shape of the wrong answer tells you which one you have. On our own demo store the first two checks passed on every page and the third one still failed on 19 products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is an empty field the same as a missing field?&lt;/strong&gt;&lt;br&gt;
No. Two of our four pages publish &lt;code&gt;category&lt;/code&gt; with an empty string in it. Anything checking whether the field is present gets a yes, so this failure arrives without an error message, a warning, or a red dot anywhere in the admin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the product type field actually matter for AI?&lt;/strong&gt;&lt;br&gt;
We measured whether it's filled and nothing else — 19 of 67 products in our demo store have it empty, and the two pages we checked with no product type also published an empty &lt;code&gt;category&lt;/code&gt; in their markup. We have no data on what that does to traffic or to how often you get recommended, and anyone quoting you a figure for that from a public catalogue read is guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long should my product descriptions be for AI to use them?&lt;/strong&gt;&lt;br&gt;
We didn't test length against any outcome, so we can't give you a target. We used 120 plain-text characters as the line between "thin" and "has something on it" — roughly a sentence. One of our own pages publishes a 52-character description. The threshold is a smoke alarm; don't write to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What percentage of Shopify stores have this problem?&lt;/strong&gt;&lt;br&gt;
We can't tell you. What we have is 42 storefronts drawn from a public list, where 35.8% of 31,767 variants had no SKU — and those 42 were, by how they ended up in our sample, a tidier-than-average group, so that figure is a floor for them, not a figure for Shopify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I check a competitor's store the same way?&lt;/strong&gt;&lt;br&gt;
The three questions, yes. The catalogue checks read public endpoints too, which is how we read 42 stores without an account on any of them. Be considerate about request volume, and note that alt text and the SEO title tag aren't exposed there, so the two fields people most want to compare are the two you can't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If I fill all these fields in, will I get recommended by ChatGPT?&lt;/strong&gt;&lt;br&gt;
We can't promise that. This post measured the state of the data, not an outcome — no rankings, no traffic, no sales. What it does establish is the order to work in: there's no point arguing about how you get recommended while two of your product pages are publishing an empty category and a 52-character description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything passed all four checks. Now what?&lt;/strong&gt;&lt;br&gt;
Then your store isn't the bottleneck and this post is done helping you. That's a genuinely different question — discovery and demand — and you should be suspicious of anyone who answers it with the same checklist they just sold you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Single-store figures: all 67 products and 4 product pages of our own demo store &lt;code&gt;fashionmore-3397.myshopify.com&lt;/code&gt;, read anonymously 2026-09-13. Multi-store figures: 42 public storefronts read 2026-08-31, first 250 products each. Sampling frame, truncation and what we could not measure are described in full in "What we measured, and what we could not" above. Last updated: 2026-09-13.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arvio: AI Store Operator — &lt;a href="https://apps.shopify.com/arvio?article_slug=chatgpt-cant-see-shopify-store&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_listing_end" rel="noopener noreferrer"&gt;install it on the Shopify App Store&lt;/a&gt;.&lt;/strong&gt; It goes through every product in the catalogue.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/chatgpt-cant-see-shopify-store?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/chatgpt-cant-see-shopify-store&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=chatgpt-cant-see-shopify-store_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Shopify Product Page Optimization: The 95% You Never Scroll To</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Fri, 11 Sep 2026 01:15:47 +0000</pubDate>
      <link>https://dev.to/arvio/shopify-product-page-optimization-the-95-you-never-scroll-to-1802</link>
      <guid>https://dev.to/arvio/shopify-product-page-optimization-the-95-you-never-scroll-to-1802</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/shopify-product-page-optimization?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-page-optimization_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-product-page-optimization&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Across 23 real Shopify storefronts (14,479 product pages), &lt;strong&gt;8,751 pages&lt;/strong&gt; tripped at least one of three very low bars: description under 200 characters, fewer than two images, or no product type set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8,337 of those 8,751 — 95.3% — sit on page 3 or deeper&lt;/strong&gt; of the store's own collection. Per-store median: 87.6% (22 stores).&lt;/li&gt;
&lt;li&gt;The cause is &lt;strong&gt;catalogue volume, not page depth&lt;/strong&gt;. In stores with 20+ collection pages, page 1 holds a median of &lt;strong&gt;1.1%&lt;/strong&gt; of the catalogue. We tested "deep pages are worse" store by store and our own data killed it.&lt;/li&gt;
&lt;li&gt;So "optimizing your product pages" is, in practice, an argument about &lt;strong&gt;coverage&lt;/strong&gt;: what share of the catalogue gets looked at, and how often.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Coverage is the whole argument&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio reads your live catalogue every night, ranks what is costing you the most, and drafts the actual edit for each product. Nothing is written until you approve it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-product-page-optimization&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-page-optimization_listing_top" rel="noopener noreferrer"&gt;See Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The 30-second version: run the same three checks on your own store
&lt;/h2&gt;

&lt;p&gt;This is read-only. No login, no app install, nothing written anywhere. Save it as &lt;code&gt;audit_catalog.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="n"&gt;shop&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                        &lt;span class="c1"&gt;# https://yourstore.com
&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;    &lt;span class="c1"&gt;# collection handle
&lt;/span&gt;&lt;span class="n"&gt;per_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;  &lt;span class="c1"&gt;# products per page on YOUR collection page
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;[^&amp;gt;]+&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                                         &lt;span class="c1"&gt;# 20 x 250 = 5,000 products
&lt;/span&gt;    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shop&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/collections/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/products.json?limit=250&amp;amp;page=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;catalog-audit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="n"&gt;bad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thin_desc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;images&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;             &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;2_images&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;  &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;per_page&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="n"&gt;deep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products               &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;per_page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; pages at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;per_page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/page)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tripped &amp;gt;=1 check      &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...of those, page 3+   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% of your problem pages)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;   page &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pg&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 audit_catalog.py https://yourstore.com all 24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to get right before you trust the page numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The third argument is your real products-per-page count&lt;/strong&gt;, not a guess. Open page 1 of that collection in a browser and count the product cards. Across our sample this ranged from &lt;strong&gt;12 to 37 per page&lt;/strong&gt; — that one number moves every "which page is this on" answer, which is why we read it out of each store's own collection HTML instead of assuming a default.&lt;/li&gt;
&lt;li&gt;The script stops at 5,000 products. Our audit stopped at 2,000 per store, which matters below.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It prints three lines and the first ten offenders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;products               &amp;lt;n&amp;gt;  (&amp;lt;n&amp;gt; pages at &amp;lt;n&amp;gt;/page)
tripped &amp;gt;=1 check      &amp;lt;n&amp;gt;  (&amp;lt;n&amp;gt;% )
...of those, page 3+   &amp;lt;n&amp;gt;  (&amp;lt;n&amp;gt;% of your problem pages)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of this post is what those three lines looked like across 23 stores that are not ours, and what we got wrong on the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we measured, and what we could not
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sample.&lt;/strong&gt; 60 Shopify storefronts. The list of 60 was drawn on 2026-08-26; every catalogue in this study was re-read fresh on &lt;strong&gt;2026-09-10&lt;/strong&gt;, the day it was analysed. All data comes from each store's public &lt;code&gt;/products.json&lt;/code&gt; and its collection page HTML. None of these are our own stores or our customers' stores, and we have no relationship with any of them — which also means we could not verify anything that isn't publicly readable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusion rule.&lt;/strong&gt; A store only enters the analysis if its largest collection runs to at least 3 pages — otherwise there is no "page 3 or deeper" bucket and the store cannot answer the question. That dropped &lt;strong&gt;37 of 60&lt;/strong&gt;: 31 too shallow, 3 with an empty collection, 2 that errored, 1 where the per-page count could not be read. &lt;strong&gt;23 storefronts, 14,479 products&lt;/strong&gt; remained. The per-store figures below are computed over 22 of those 23: one store is absent from the paired computation and we could not reconstruct which, so we report the smaller number rather than rounding it up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The three checks&lt;/strong&gt;, each deliberately set at a floor rather than at "good":&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Threshold&lt;/th&gt;
&lt;th&gt;Why this bar&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Thin description&lt;/td&gt;
&lt;td&gt;plain-text body under &lt;strong&gt;200 characters&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Roughly two sentences. Below this there is nothing on the page to read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Too few images&lt;/td&gt;
&lt;td&gt;fewer than &lt;strong&gt;2&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;One image means no second angle, no scale shot, no detail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No product type&lt;/td&gt;
&lt;td&gt;field empty&lt;/td&gt;
&lt;td&gt;It's the field your collections, filters, and product feeds hang off&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A page counts as a problem page if it trips &lt;strong&gt;any one&lt;/strong&gt; of the three.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Position.&lt;/strong&gt; Rank is the product's position in the store's own collection order — the order a shopper pages through — and "which page" uses the real per-page count from that store's collection HTML.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Truncation.&lt;/strong&gt; Four stores were cut off at &lt;strong&gt;2,000 products&lt;/strong&gt;. The part that got cut is the deep end, so those four contribute all of their page-1 products and only some of their deep ones. &lt;strong&gt;95.3% is therefore a lower bound&lt;/strong&gt;, not a point estimate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not measured: image alt text and the SEO title tag.&lt;/strong&gt; &lt;code&gt;/products.json&lt;/code&gt; does not expose either field, so we have no data on them and this post makes no claim about them in either direction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conflict of interest.&lt;/strong&gt; We make a Shopify agent. The finding below is the argument for a category of tool we happen to sell, The numbers here are ours; what you can reproduce is the method — the script above runs the same three checks on any stores you pick.&lt;/p&gt;

&lt;h2&gt;
  
  
  8,337 of 8,751 problem pages sit past page 3
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product pages analysed&lt;/td&gt;
&lt;td&gt;14,479&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tripped at least one check&lt;/td&gt;
&lt;td&gt;8,751&lt;/td&gt;
&lt;td&gt;60.4% of all pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...of those, on page 3 or deeper&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8,337&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;95.3%&lt;/strong&gt; of problem pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-store median (22 stores)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;87.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two readings of that 60.4% are both true and worth separating. One: three out of five product pages in this sample fail a bar that is genuinely low. Two: these are floors, not standards — a page with a 210-character description and two images passes every check here and can still be a bad page. We chose bars we could measure identically across 23 strangers' stores, which is the same reason they are blunt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mechanism is catalogue volume, not page depth
&lt;/h2&gt;

&lt;p&gt;Here is the article we expected to write: &lt;em&gt;products get worse the deeper they sit, because merchants set up the first page carefully and lose steam.&lt;/em&gt; It is intuitive, and the aggregate numbers appeared to support it.&lt;/p&gt;

&lt;p&gt;Then we compared each store against &lt;strong&gt;itself&lt;/strong&gt; — its own page 1 versus its own page 3+ — and the effect vanished:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Deep worse&lt;/th&gt;
&lt;th&gt;Deep better&lt;/th&gt;
&lt;th&gt;Tied&lt;/th&gt;
&lt;th&gt;Median difference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Empty description&lt;/td&gt;
&lt;td&gt;6 stores&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.0pp&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thin description&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.0pp&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No product type&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.0pp&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fewer than 2 images&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.07pp&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Empty description&lt;/em&gt; is the zero-character subset of &lt;em&gt;thin description&lt;/em&gt;, reported on its own line because it is the case with nothing on the page at all — the three checks are still three.&lt;/p&gt;

&lt;p&gt;Ten stores worse, eight better, four tied is not a pattern. Deep pages in these catalogues are &lt;strong&gt;not&lt;/strong&gt; systematically worse than the shallow pages in the same store.&lt;/p&gt;

&lt;p&gt;The 95.3% is arithmetic. In the stores with 20 or more collection pages, page 1 is a rounding error:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Catalogue size&lt;/th&gt;
&lt;th&gt;Products/page&lt;/th&gt;
&lt;th&gt;Pages&lt;/th&gt;
&lt;th&gt;Page 1 as share of catalogue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2,000 *&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;167&lt;/td&gt;
&lt;td&gt;0.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,508&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;td&gt;0.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2,000 *&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,576&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;1.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2,000 *&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;84&lt;/td&gt;
&lt;td&gt;1.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2,000 *&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;1.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;601&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;4.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;395&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;4.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;*&lt;/code&gt; = truncated at 2,000; the true catalogue is larger and the true page-1 share is smaller still. Median across these eight: &lt;strong&gt;1.1%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your catalogue is roughly uniformly mediocre. It just happens that around 99% of it sits past page 1, where nobody scrolls — including you. That 99% is a share of all pages; the 95.3% above is a share of the problem pages. Two different counts.&lt;/p&gt;

&lt;p&gt;That reframes the job. The question stops being &lt;em&gt;how do I optimize a product page&lt;/em&gt; (you already know: write the description, add the second image, set the type) and becomes &lt;em&gt;what walks all 1,576 of them.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: the Shopify admin, one product at a time
&lt;/h2&gt;

&lt;p&gt;This is the version that cannot be wrong about the page, because you are looking at the actual page while you write. For a store in the shallow half of our sample — one or two collection pages — this is not just adequate, it's the correct answer, and you should close this post and go do it.&lt;/p&gt;

&lt;p&gt;It stops for a reason that has nothing to do with quality. At 99 pages, "I'll do a few every evening" is a plan with an end date measured in seasons, and the pages you edited in week one are not the pages your catalogue looked like by week nine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: CSV export and re-import
&lt;/h2&gt;

&lt;p&gt;Export, edit in a spreadsheet, re-import. This is the right shape when you already know the value you want in a column — a supplier's product types, a batch of vendor names, tags you can derive from the handle.&lt;/p&gt;

&lt;p&gt;It is the wrong shape for descriptions, because the spreadsheet doesn't know anything about the product. You still have to &lt;a href="https://arvio.a.xyz/blog/product-description-examples-shopify?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-page-optimization_blog2" rel="noopener noreferrer"&gt;write every one of those descriptions yourself&lt;/a&gt;, and a spreadsheet full of near-identical descriptions is worse than a thin one.&lt;/p&gt;

&lt;p&gt;One thing to verify before you trust a round trip: export a collection, change exactly one product, re-import, and diff the result against what you exported. You want to see for yourself which columns a re-import writes and which it leaves alone, on your store, before you do it to 1,500 products.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 3: something that covers the whole catalogue, nightly
&lt;/h2&gt;

&lt;p&gt;If the problem is coverage, the tool has to visit every product — including the ones you have never opened.&lt;/p&gt;

&lt;p&gt;That is the shape Arvio takes. It's an AI store agent: it scans your store daily, ranks the gaps by what it estimates each one is costing you — a ranking that runs on your own store's data, which a public-catalogue study like this one cannot see — and then &lt;strong&gt;writes the actual edits against your live catalogue&lt;/strong&gt; — the description, the missing product type — and holds each one for your approval. Nothing changes until you approve it, and every edit can be undone. It reads across Shopify plus connected tools like Klaviyo and Google Ads, so the ranking isn't blind to what's happening outside the product page.&lt;/p&gt;

&lt;p&gt;A list is not a fix: an audit tool that hands you a list of problem pages has not written a single description. The list was never the hard part — you could have generated it in this post's first section, in 30 seconds, for nothing.&lt;/p&gt;

&lt;p&gt;Arvio also isn't a bulk editor with AI bolted on. Bulk editing is one of the things it can do once it knows what's wrong; it is not the reason it exists.&lt;/p&gt;

&lt;p&gt;Across the 23 stores we read, 95.3% of the problem pages sat past page 3, at a per-store median of 87.6%. Your own split will be its own number. The point of a nightly pass is that the far end of the catalogue gets the same attention as the first screen, without you having to remember it exists.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-product-page-optimization&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-page-optimization_listing_mid" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Five ways this goes wrong
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Optimizing what you can see.&lt;/strong&gt; Page 1 is a median 1.1% of a deep catalogue. A page-1 sweep is a real improvement to 1.1% of your product pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixing by template.&lt;/strong&gt; Hundreds of pages carrying the same rewritten paragraph with the product name swapped in clears our thin-description check and helps nobody. The check is a floor; passing it is not the goal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counting edits instead of coverage.&lt;/strong&gt; "1,200 products updated this month" doesn't say whether the same 1,200 got updated four times. Measure the share of the catalogue that was looked at, and when it was last looked at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditing again instead of fixing.&lt;/strong&gt; A second audit produces a second list. The gap between "we know" and "it's written" is the entire job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusting a spot check.&lt;/strong&gt; Our own aggregate buckets pointed confidently at a depth effect that does not exist — 80% of the deep-page sample came from six large stores, so we were reading store mix and calling it depth. Compare a store against itself before you believe a pattern.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When a nightly agent is the wrong tool for you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your catalogue is small.&lt;/strong&gt; 31 of the 60 storefronts we sampled didn't have three collection pages. If that's you, an afternoon in the admin beats any tool, and you'll write better copy than any of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every word needs legal or clinical review.&lt;/strong&gt; If each change has to be signed off by a human specialist, an approval queue that fills up nightly is a new bottleneck, not a saved afternoon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You already know the exact values you want.&lt;/strong&gt; Bulk price changes, a tag migration, a vendor rename — that's a spreadsheet operation. Use a bulk editor; it's the right tool and it's cheaper.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What counts as an optimized Shopify product page?&lt;/strong&gt;&lt;br&gt;
There's no single definition, so for this study we used three floors we could check identically across 23 strangers' stores: a description over 200 plain-text characters, &lt;a href="https://arvio.a.xyz/blog/shopify-how-many-product-images?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-page-optimization_blog4" rel="noopener noreferrer"&gt;at least two images&lt;/a&gt;, and &lt;a href="https://arvio.a.xyz/blog/shopify-product-type-missing?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-page-optimization_blog5" rel="noopener noreferrer"&gt;a product type set&lt;/a&gt;. 60.4% of the 14,479 pages we looked at failed at least one. Clearing all three doesn't make a page good; failing one means something is definitely missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many images should a Shopify product have?&lt;/strong&gt;&lt;br&gt;
We only tested the floor of two, and we did not measure what number performs best — that would need conversion data we don't have for other people's stores. What the data shows is how common the floor failure is, not the optimum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long should a Shopify product description be?&lt;/strong&gt;&lt;br&gt;
We used 200 characters as the line between "thin" and "has something on it," which is roughly two sentences. Treat it as a smoke alarm, not a target. We did not test description length against any outcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the product type field actually matter?&lt;/strong&gt;&lt;br&gt;
We measured whether it's filled, and nothing else. It's the field collections, filters, and product feeds are built on, so an empty one has downstream effects inside your own store — but we have no data here on what it does to traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are older or deeper products worse than the ones on page 1?&lt;/strong&gt;&lt;br&gt;
We tested exactly this, store by store, and found no systematic difference: across 22 stores the median gap between page 1 and page 3+ was 0.0pp on three of the four rows above, and the direction disagreed store to store. The reason most problem pages are deep is that most &lt;em&gt;pages&lt;/em&gt; are deep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will fixing these pages improve my search rankings?&lt;/strong&gt;&lt;br&gt;
We can't tell you that, and anyone who does from a catalogue audit is guessing. This study measured the state of 14,479 product pages and where they sit. It measured no outcome — no rankings, no traffic, no revenue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I run this on a competitor's store?&lt;/strong&gt;&lt;br&gt;
Yes. &lt;code&gt;/products.json&lt;/code&gt; is a public read-only endpoint — that's how we read 60 storefronts without an account on any of them. Be considerate about request volume, and note that the two fields people most want to compare, alt text and the SEO title tag, aren't in it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does it take to fix a catalogue at this scale by hand?&lt;/strong&gt;&lt;br&gt;
We didn't measure a per-page time, so any number here would be invented. The ratio is the more useful figure: at a median 1.1% of the catalogue on page 1, a manual pass over the products you actually look at leaves roughly 99 out of 100 untouched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arvio: AI Store Operator — &lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-product-page-optimization&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-page-optimization_listing_end" rel="noopener noreferrer"&gt;install it on the Shopify App Store&lt;/a&gt;.&lt;/strong&gt; It visits every product in the catalogue rather than the ones on page 1, and drafts the missing description or product type for each one for you to approve. From $9.90/mo, with a 5-day free trial.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sample of 60 public Shopify storefronts drawn 2026-08-26; every catalogue re-read fresh, analysed and published 2026-09-10. Sample, exclusion rules, thresholds and truncation are described in full in "What we measured, and what we could not" above. Last updated: 2026-09-10.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/shopify-product-page-optimization?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-page-optimization_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-product-page-optimization&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-page-optimization_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your Shopify store gets traffic but no sales — start with the pages you never scroll to</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Fri, 11 Sep 2026 01:15:42 +0000</pubDate>
      <link>https://dev.to/arvio/your-shopify-store-gets-traffic-but-no-sales-start-with-the-pages-you-never-scroll-to-4hef</link>
      <guid>https://dev.to/arvio/your-shopify-store-gets-traffic-but-no-sales-start-with-the-pages-you-never-scroll-to-4hef</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/shopify-store-not-converting?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-store-not-converting&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Published September 8, 2026 by &lt;a href="https://apps.shopify.com/partners/adot-inc?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_listing1" rel="noopener noreferrer"&gt;Adot Technologies Inc&lt;/a&gt;, the team behind Arvio. The store list was drawn at random from a public source on August 26, 2026. We have read it twice since: field completeness on September 1, 2026, and catalogue depth on September 8, 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The advice you have already read — homepage, theme, cart, checkout — is about the pages you look at every day. This piece is about the ones you don't.&lt;/li&gt;
&lt;li&gt;Your catalogue is deeper than that. On the stores we could measure, the biggest collection ran a median of &lt;strong&gt;7 pages&lt;/strong&gt; and the deepest ran &lt;strong&gt;241&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;How many products fit on a page is set by your theme, not by Shopify. We measured 10–48 products per page across those stores, so "page 3" means a different depth on every store.&lt;/li&gt;
&lt;li&gt;The fix is to sort your catalogue by what is missing and read the end of that list rather than the top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What we did not measure:&lt;/strong&gt; whether fixing those pages moves your conversion rate. We haven't run a controlled test, and nothing below claims a number for it.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You can only fix what you can see&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio reads your product data past the first page, shows you where it comes up empty, and drafts the fill for you to approve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-store-not-converting&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_listing_top" rel="noopener noreferrer"&gt;See Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The quick version
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check where your traffic actually lands before you change anything. If it's landing on product pages you can't remember writing, that's the case this article is about — and it is the one most checklists skip, because those pages are also the ones the person writing the checklist never sees.&lt;/li&gt;
&lt;li&gt;Find those pages by sorting, not by scrolling: the Shopify admin can filter your product list by what is missing, and a CSV export can sort by it.&lt;/li&gt;
&lt;li&gt;Fix the data on the pages people actually land on first, then work down the list. The order matters more than the volume.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Your catalogue is longer than anything you look at
&lt;/h2&gt;

&lt;p&gt;On the 18 live storefronts where we could count it, the biggest collection ran a median of &lt;strong&gt;7 pages&lt;/strong&gt; of products, and the deepest ran &lt;strong&gt;241&lt;/strong&gt;. Advice about a store that is not converting is written for the pages an owner sees — and almost none of that depth is one of them.&lt;/p&gt;

&lt;p&gt;(If what you want is the per-page checklist rather than the depth argument, we published one with base rates from live stores: &lt;a href="https://arvio.a.xyz/blog/shopify-product-page-audit?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_blog3" rel="noopener noreferrer"&gt;a Shopify product page audit checklist&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;We took live Shopify storefronts from a public list, opened the biggest collection on each one, and counted how many products the theme puts on a single page. Then we divided the size of that collection by that number.&lt;/p&gt;

&lt;p&gt;The five deepest of the 18 stores we could count — &lt;strong&gt;not&lt;/strong&gt; a typical store; the median is four rows below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Products in the biggest collection&lt;/th&gt;
&lt;th&gt;Products per page (measured)&lt;/th&gt;
&lt;th&gt;Pages&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2,883&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;241&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,838&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;154&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,919&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;120&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,489&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;403&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Across all 18 stores we could count, the median was &lt;strong&gt;7 pages&lt;/strong&gt; and 13 of them ran deeper than three pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Page size is a theme decision, not a Shopify decision.&lt;/strong&gt; Shopify's own &lt;a href="https://shopify.dev/docs/api/liquid/tags/paginate" rel="noopener noreferrer"&gt;Liquid &lt;code&gt;paginate&lt;/code&gt; documentation&lt;/a&gt; describes the tag as taking &lt;em&gt;"The number of array items to include per page, between 1 and 250."&lt;/em&gt; (read August 29, 2026) — that is a ceiling, not a default. In our reading, stores landed anywhere between 10 and 48 products per page. So "it's on page 2" is not a fixed amount of scrolling; on one store page 2 starts at product 11, on another at product 49.&lt;/p&gt;

&lt;p&gt;If products seem to be missing from a collection entirely rather than just sitting deep in it, that is a different problem with a different cause — we wrote it up separately in &lt;a href="https://arvio.a.xyz/blog/shopify-collection-not-showing-all-products?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_blog4" rel="noopener noreferrer"&gt;why a Shopify collection isn't showing all its products&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where a product sits in your collection is not where a visitor enters.&lt;/strong&gt; Someone arriving from search lands on whichever product page matched their query. That page may be one you have read a hundred times, or one you last saw the day you imported it — the collection position that decides whether &lt;em&gt;you&lt;/em&gt; ever scroll to it does not decide which page &lt;em&gt;they&lt;/em&gt; land on.&lt;/p&gt;

&lt;h3&gt;
  
  
  How we measured this
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Sample.&lt;/strong&gt; 18 live Shopify storefronts, read on September 8, 2026, taken at random from a public list of stores whose owners had posted their own URL on the Shopify Community's Store Feedback board. Everything here came from endpoints any browser can fetch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whether it looks like you.&lt;/strong&gt; These are stores whose owners posted their own URL asking for feedback, so they skew newer and less established than a random Shopify store. On the September 1 reading of all 39 of them, the median catalogue held 130 products — but the 18 deep enough for us to count pages on ran larger than that, so the depth figures below describe the bigger end of the list, not a typical small store. Stores that had already stopped serving a storefront dropped out when the list was built, so what is left is the ones still trading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The funnel.&lt;/strong&gt; 39 storefronts in the public list carried products; we opened the biggest collection on each. 18 let us count page size from the storefront itself and are the basis for every page-depth number below. The other 21 could not be counted — 15 returned overlapping products on both pages, 2 rendered no product links we could read, 2 returned nothing on page two while the collection was larger, 2 ignored the page parameter — and are excluded rather than counted as one page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The exclusion rule, fixed before we looked at the results.&lt;/strong&gt; If we could not confirm from the storefront itself how many products a page holds, that store does not get a page count — which happens when the theme ignores the &lt;code&gt;?page=&lt;/code&gt; parameter, when it loads more products by scrolling instead of paging, or when the store rate-limited us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: use the admin's filters instead of scrolling
&lt;/h2&gt;

&lt;p&gt;The Shopify admin can narrow your catalogue by the fields it indexes. It filters on values that exist, not on emptiness — so use it to cut the catalogue into slices, then read the &lt;em&gt;end&lt;/em&gt; of each slice, which is where the products you forgot about sit.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Products&lt;/strong&gt; in the admin.&lt;/li&gt;
&lt;li&gt;Use the search-and-filter bar to filter by &lt;strong&gt;Product type&lt;/strong&gt;, &lt;strong&gt;Vendor&lt;/strong&gt;, &lt;strong&gt;Tagged with&lt;/strong&gt;, &lt;strong&gt;Collection&lt;/strong&gt;, or &lt;strong&gt;Status&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Sort the result by &lt;strong&gt;Product title A–Z&lt;/strong&gt; and jump to the &lt;em&gt;last&lt;/em&gt; page rather than reading from the top.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What this method is good at: it costs nothing extra, it is immediate, and it works on any plan. What it is not good at: the admin filters on fields that &lt;em&gt;exist&lt;/em&gt;. There's no filter for "the description is two sentences long", or for "this description was written for a different product".&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: export a CSV and sort by what is missing
&lt;/h2&gt;

&lt;p&gt;For anything the admin cannot filter on, export and sort.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Products → Export&lt;/strong&gt;, then choose &lt;em&gt;"CSV file for Excel, Numbers, or another spreadsheet program"&lt;/em&gt; (&lt;a href="https://help.shopify.com/en/manual/products/import-export/export-products" rel="noopener noreferrer"&gt;exporting products&lt;/a&gt;, read September 8, 2026).&lt;/li&gt;
&lt;li&gt;Open the file and add a column with a formula for the thing you care about — for example the character length of the description column (&lt;code&gt;Description&lt;/code&gt; in the current template; older exports call it &lt;code&gt;Body (HTML)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Sort ascending. The rows at the top are the product pages a visitor is most likely to bounce off of.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is where CSV work usually goes wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each variant gets its own row. Shopify's &lt;a href="https://help.shopify.com/en/manual/products/import-export/using-csv" rel="noopener noreferrer"&gt;product CSV guide&lt;/a&gt; describes it from the writing side: &lt;em&gt;"enter all the fields for the product in the first row along with the URL for the first image. In the following rows, enter the URL handle. Then skip the **Title&lt;/em&gt;&lt;em&gt;, **Description&lt;/em&gt;&lt;em&gt;, **Vendor&lt;/em&gt;&lt;em&gt;, and **Tags&lt;/em&gt;* columns."* (read September 8, 2026) So the rows after the first one are blank in exactly the columns you want to measure. Keep only the first row of each &lt;code&gt;Handle&lt;/code&gt; before you sort.&lt;/li&gt;
&lt;li&gt;That column holds HTML, so a "200-character" description can be mostly markup. Strip tags before you measure length, or you'll rank a &lt;code&gt;&amp;lt;p&amp;gt;&amp;lt;br&amp;gt;&amp;lt;/p&amp;gt;&lt;/code&gt; above a real sentence.&lt;/li&gt;
&lt;li&gt;Re-importing matches on &lt;code&gt;Handle&lt;/code&gt;: &lt;em&gt;"when the handle in the import CSV file matches an existing handle in your products list, the values in the CSV file overwrite the values in the matching columns."&lt;/em&gt; (&lt;a href="https://help.shopify.com/en/manual/products/import-export/using-csv" rel="noopener noreferrer"&gt;product CSV guide&lt;/a&gt;, read September 8, 2026.) Shopify also warns that &lt;em&gt;"sorting your CSV file in a spreadsheet program can cause product variants or image URLs to become disassociated"&lt;/em&gt; (&lt;a href="https://help.shopify.com/en/manual/products/import-export/export-products" rel="noopener noreferrer"&gt;exporting products&lt;/a&gt;, read September 8, 2026) — so sort a working copy for analysis, and never re-import that copy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Method 3: have something walk the whole catalogue
&lt;/h2&gt;

&lt;p&gt;Both methods above have the same ceiling: you still have to decide what to look at, and you still have to write the fix yourself. For a catalogue that runs 7 pages that is an afternoon; for one that runs 241 it is not something you do once a quarter.&lt;/p&gt;

&lt;p&gt;This is the part Arvio does. It reads the catalogue on your live store, flags where product data is missing or thin, and drafts the replacement text for you to approve — product by product. It's an operator you review, not a bulk find-and-replace you run and hope about.&lt;/p&gt;

&lt;p&gt;It drafts; you approve. If you want something that rewrites 2,000 products without you reading any of them, this is the wrong shape of tool. And it only sees your product data — it can't tell you your pricing is wrong or that your photography is the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-store-not-converting&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_listing_mid" rel="noopener noreferrer"&gt;See Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Five things that quietly go wrong past page one
&lt;/h2&gt;

&lt;p&gt;A separate reading of the same stores on 2026-09-01 — 20,659 products across 39 storefronts — says something more useful than an average. Missing product data is not spread thinly across a catalogue. It is closer to a switch that is either on or off for the whole store.&lt;/p&gt;

&lt;p&gt;36% of all those products had no product type, while the median store was missing it on 9% of its catalogue. Both numbers are true, and the gap between them is the finding: three stores accounted for 75% of every missing value, and inside those three the field was empty on 85–99% of the entire catalogue. Counting stores instead of products splits them: of the 39, 15 were missing it on more than half their products and 18 on under 5% — only 6 sat anywhere in between. Catalogue size sorts those two groups the opposite way to the raw count — stores holding 72 products or fewer were missing it on a median of 46% of their catalogue, stores above that on 2%. Thin descriptions follow the same shape rather than being sprinkled evenly.&lt;/p&gt;

&lt;p&gt;Before you start reading individual products, check whether a field is empty on &lt;em&gt;everything&lt;/em&gt; — that is usually one import that did not carry the column, and it is one fix rather than a thousand.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Descriptions that stop after one line.&lt;/strong&gt; (&lt;a href="https://arvio.a.xyz/blog/product-description-examples-shopify?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_blog6" rel="noopener noreferrer"&gt;Product description examples&lt;/a&gt; if you want a shape to copy.) Usually the product was added in a hurry, or imported from a supplier feed that had nothing in the description field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No product type.&lt;/strong&gt; (&lt;a href="https://arvio.a.xyz/blog/shopify-product-type-missing?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_blog7" rel="noopener noreferrer"&gt;How to add a product type in Shopify&lt;/a&gt; covers the field itself.) An empty &lt;code&gt;Product type&lt;/code&gt; keeps the product out of every filter and automated collection rule built on that field — other filters, like Vendor or Status, still find it, but the merchandising that depends on type does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images that were never re-checked.&lt;/strong&gt; A product that has one photo has one chance to answer "is this the thing I searched for".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy written for a different variant.&lt;/strong&gt; Very common after a catalogue import: the description describes the parent product, and every size or colour repeats it word for word.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Products that are still listed but no longer supplied.&lt;/strong&gt; They keep their search ranking, take the click, and answer with a page nobody maintains.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these are conversion tricks. They are the reason a visitor who arrived with intent leaves without a reason to stay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three situations where this really is the problem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You get search traffic to product pages but the homepage numbers look fine.&lt;/strong&gt; Check the landing pages in your analytics, not the sessions total. If the product pages taking the traffic are ones you can't remember writing, that's this problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your catalogue arrived in bulk.&lt;/strong&gt; Imported from another platform, a supplier feed or a CSV — we have a longer piece on &lt;a href="https://arvio.a.xyz/blog/shopify-fix-catalog-after-import?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_blog8" rel="noopener noreferrer"&gt;cleaning up product data after an import&lt;/a&gt;. Whatever the source file did not carry, your store does not have — and the gaps are distributed across the whole catalogue, not the front of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sales are concentrated in a handful of products.&lt;/strong&gt; If most of your revenue comes from the products you personally wrote, that is not proof the rest are bad products. It is what you would expect if the rest were never finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there public data on how deep Shopify catalogues run?
&lt;/h2&gt;

&lt;p&gt;We are not aware of a published figure, which is why we measured it ourselves. On September 8, 2026 we read 39 live Shopify storefronts taken from a public list and counted page size directly from the storefront on the 18 that let us: the biggest collection ran a median of 7 pages, the deepest ran 241, and themes were putting between 10 and 48 products on a page. The exclusion rule and the full funnel are in the method section above — 21 of the 39 could not be counted and are left out of every depth figure rather than counted as one page.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this is not your problem
&lt;/h2&gt;

&lt;p&gt;Which of these are you? The fix is different for each.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small catalogue.&lt;/strong&gt; If your whole store fits on one or two pages, you've already read all of it. Your conversion problem is somewhere else — price, shipping, trust, or the traffic itself being the wrong traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No traffic to product pages at all.&lt;/strong&gt; If visitors land on the homepage and leave, deeper product pages are not what is stopping them. Work on where the traffic comes from first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic that was never going to buy.&lt;/strong&gt; If the visitors arriving were never in the market, better product pages won't turn them into buyers — check what the traffic is before you rewrite what it lands on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You already have complete product data.&lt;/strong&gt; Some stores do. In that case the catalogue is not where your answer is, and nothing in this article will help.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you want the catalogue walked for you
&lt;/h2&gt;

&lt;p&gt;Everything above is doable by hand, and for a store of a few dozen products doing it by hand is the right call. The reason to automate it is depth: the further your catalogue runs past what you can read, the less likely it is that you will ever get to the end of the list on your own.&lt;/p&gt;

&lt;p&gt;That is the case Arvio is built for: it works down the list you would never reach, and you approve each draft before it goes anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-store-not-converting&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_listing_end" rel="noopener noreferrer"&gt;See Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why is my Shopify store getting traffic but no sales?
&lt;/h3&gt;

&lt;p&gt;The usual answers — price, trust, shipping cost, checkout friction — are all real. This article is about the one that is hardest to see: visitors landing on product pages the owner has never read, because those pages sit past the first page of a collection. Check which pages your traffic actually lands on before assuming it is the homepage.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many products does Shopify show on a collection page?
&lt;/h3&gt;

&lt;p&gt;Shopify doesn't set it — your theme does. The &lt;a href="https://shopify.dev/docs/api/liquid/tags/paginate" rel="noopener noreferrer"&gt;Liquid &lt;code&gt;paginate&lt;/code&gt; tag&lt;/a&gt; takes &lt;em&gt;"The number of array items to include per page, between 1 and 250."&lt;/em&gt; (read August 29, 2026) In our September 2026 reading of live storefronts, themes were using between 10 and 48.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I find products with missing descriptions in Shopify?
&lt;/h3&gt;

&lt;p&gt;The admin can't filter on description length. Export your products to CSV, strip the HTML from the description column, add a length column and sort ascending. Keep one row per &lt;code&gt;Handle&lt;/code&gt; first — each variant gets its own row, and the rows after the first are blank in the description column.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does thin product copy actually affect conversion?
&lt;/h3&gt;

&lt;p&gt;It affects whether a visitor who landed with intent has a reason to keep reading. We haven't tested that. What we measured is how deep a catalogue runs — 7 pages at the median, on the stores where we could count it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What else is usually empty down there?
&lt;/h3&gt;

&lt;p&gt;Meta descriptions, most often — they are invisible on the page itself, so nothing prompts you to write them. We measured how many were missing across live stores in &lt;a href="https://arvio.a.xyz/blog/shopify-missing-meta-descriptions?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_blog10" rel="noopener noreferrer"&gt;Shopify meta descriptions&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I rewrite my whole catalogue?
&lt;/h3&gt;

&lt;p&gt;No. Sort by what is missing and start with the products that already take traffic. A product page nobody lands on doesn't get more valuable because it's well written.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is this a Shopify problem?
&lt;/h3&gt;

&lt;p&gt;No — it is a consequence of pagination, which every catalogue on every platform has. Shopify's admin filters are good at the fields that exist; the gap is that "thin" and "empty" are not the same thing, and only one of them is filterable.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the fastest way to see how deep my own catalogue goes?
&lt;/h3&gt;

&lt;p&gt;Open your largest collection, count the products on its first page, and divide the number of products in that collection by that number. That is the number of pages your store has. Most owners have read the first one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/shopify-store-not-converting?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-store-not-converting&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-store-not-converting_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Add Product Type in Shopify When the Field Is Not on the Product Page</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:10:36 +0000</pubDate>
      <link>https://dev.to/arvio/how-to-add-product-type-in-shopify-when-the-field-is-not-on-the-product-page-49fc</link>
      <guid>https://dev.to/arvio/how-to-add-product-type-in-shopify-when-the-field-is-not-on-the-product-page-49fc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/shopify-product-type-missing?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-type-missing_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-product-type-missing&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you opened a product, scrolled to the Organization card, and found Category, Vendor and Tags but no &lt;strong&gt;Product type&lt;/strong&gt;, you did not miss it. On 2026-09-05 we read the full text of a product page in the current Shopify admin, including the content inside its shadow roots, and the string "Product type" appeared &lt;strong&gt;zero times&lt;/strong&gt; anywhere you could see or click. That is one admin, on one store, on one day: if yours still shows the field, you are on an admin that has not changed yet, and the one-product route still works for you.&lt;/p&gt;

&lt;p&gt;The field still exists. Products still carry a &lt;code&gt;product_type&lt;/code&gt; value, and the endpoint that publishes a storefront's catalogue still returns it. The input box is still in that page's markup, but on the day we looked it sat inside a popover set to &lt;code&gt;visibility:hidden&lt;/code&gt; — present, and not something you can click. The place it still has one is the bulk editor, which is the better place anyway: nobody with a real catalogue has one product to fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Product type field was not reachable on the product page in the admin we read on 2026-09-05. If that matches what you are seeing, the one-product-at-a-time route is closed to you.&lt;/li&gt;
&lt;li&gt;The bulk editor still has a Product type column: &lt;code&gt;/bulk?resource_name=Product&amp;amp;edit=product_type&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Sorting the Products list by Product type ascending floats every blank one to the top. That finds the gaps without exporting a CSV.&lt;/li&gt;
&lt;li&gt;Product type and the newer &lt;strong&gt;Category&lt;/strong&gt; (Shopify's taxonomy) are two separate fields. Filling one does not fill the other.&lt;/li&gt;
&lt;li&gt;In 42 public storefronts we read on 2026-09-05, &lt;strong&gt;2,054 of 5,937 products had no product type — 34.6% by product, and 13.5% at the median store&lt;/strong&gt;, and &lt;strong&gt;24 of the 42 stores had some products typed and some not&lt;/strong&gt;. The two rates answer different questions and are not interchangeable — see the measurement section.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The quick version
&lt;/h2&gt;

&lt;p&gt;Three steps, then you start typing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Size the job.&lt;/strong&gt; Open Products in your admin and sort by &lt;strong&gt;Product type&lt;/strong&gt;, ascending. Products with no type sort first, so you can see how many there are and whether they arrived together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open the bulk editor on that one field.&lt;/strong&gt; Your admin URL looks like &lt;code&gt;admin.shopify.com/store/your-store/products&lt;/code&gt;. Replace everything from &lt;code&gt;/products&lt;/code&gt; onward with:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   /bulk?resource_name=Product&amp;amp;edit=product_type
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That opens a spreadsheet-style grid with a Product type column that accepts typing. It loads its own list of products — the sort you just did does not carry over, so keep the Products tab open if you want to refer back to it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Type and save.&lt;/strong&gt; Type the value into the Product type cell, then save.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the mechanism. The rest of this page covers the parts that go wrong: which field you are actually editing, how to avoid inventing a pile of near-duplicate types on the way through, and how much of a catalogue this usually affects.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your catalogue already knows which types are missing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio reads your live catalogue, finds the products with no product type, and proposes one for each based on that product's own title, tags and variants. Nothing is written until you approve it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-product-type-missing&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-type-missing_listing_top" rel="noopener noreferrer"&gt;See Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Product type and Category are two different fields
&lt;/h2&gt;

&lt;p&gt;This is the single most common way the job gets done wrong, and it costs you the session, because you finish and the blanks are still blank.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Category&lt;/strong&gt; is Shopify's standard taxonomy. You pick from a controlled tree, the values are the same in every store, and the platform uses it for things that need a shared vocabulary across all of Shopify. Category is on the product page, which is part of why people assume it replaced the older field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product type&lt;/strong&gt; is your own user-defined label. You type whatever you want. Nobody validates it, nothing autocorrects it, and no two stores need to agree on it. It is the field that ends up in your automated collection conditions and in whatever internal filtering you have built for yourself over the years.&lt;/p&gt;

&lt;p&gt;Two things follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting a Category on a product does &lt;strong&gt;not&lt;/strong&gt; give it a product type. If you spent a session filling in categories and your collections still look wrong, this is why.&lt;/li&gt;
&lt;li&gt;A product with a blank product type is not an uncategorised product. It may well have a Category. Those are different columns, and this page is only about one of them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So: if your store has automated collections, theme filters or exports that read Product type, the blank rows are the ones behaving strangely. If nothing in your store reads it, blank costs you nothing. Check before you spend the afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding which products are blank without exporting a CSV
&lt;/h2&gt;

&lt;p&gt;The usual advice is to export to CSV, filter the empty column, fix, re-import. That works, and it is also the route with the most ways to damage a catalogue: a re-import writes every column in the file, not the one you meant to change.&lt;/p&gt;

&lt;p&gt;The Products list already does the finding part.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkx8t6ltqkvjhinc5e03n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkx8t6ltqkvjhinc5e03n.png" alt="Sort the Products list by **Product type** ascending — every product with a blank type floats to the top." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sorting ascending puts empty values first. You now have three things you did not have a minute ago: how many blanks there are, which products they are, and whether they cluster (one supplier, one import, one collection) or are scattered at random. That clustering question decides how you fill them. A block arriving from one supplier feed usually wants a single type applied down the block. The same products scattered across the catalogue are that many separate decisions.&lt;/p&gt;

&lt;p&gt;Sorting only changes what you are looking at. The list still holds everything else, so check the order is still the one you set before you trust the top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bulk editor is where the field still has an input box
&lt;/h2&gt;

&lt;p&gt;Open the URL from the quick version. The &lt;code&gt;edit=product_type&lt;/code&gt; part is what makes the grid show that column rather than the default set, which matters: a bulk editor showing every column it can show is that many columns of accidents waiting for a mistyped paste.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fme8f0jsc5mb91csexsp8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fme8f0jsc5mb91csexsp8.png" alt="The bulk editor at  raw `/bulk?resource_name=Product&amp;amp;edit=product_type` endraw  — three columns: Product title · Product category · **Product type**." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Product title is there so you can tell what you are typing about. Product category is there so you can see the taxonomy value beside the user-defined one: a row can carry a Category and still have no Product type, which looks broken until you know the two fields are unrelated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to actually type.&lt;/strong&gt; The value is your answer to "what is this", in the noun a customer would use, at the level that groups several products. &lt;code&gt;Mug&lt;/code&gt; groups things; &lt;code&gt;Blue 350ml Stoneware Mug&lt;/code&gt; groups nothing. If the label describes an attribute instead of the thing (&lt;code&gt;Sale&lt;/code&gt;, &lt;code&gt;New&lt;/code&gt;, &lt;code&gt;Summer&lt;/code&gt;), it belongs in Tags, because each product gets one product type and spending it on a season leaves nothing durable behind.&lt;/p&gt;

&lt;p&gt;How to work the grid without making it worse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reuse before you invent.&lt;/strong&gt; Check the types your catalogue already uses and copy one of them exactly. Every fresh string you type creates a new product type, and nothing in the store will tell you that you meant the one you created last month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fill in clusters.&lt;/strong&gt; If a block of products came from one supplier or one import, do that block in one pass while the decision is still loaded in your head.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save as you go.&lt;/strong&gt; Save in batches instead of at the end. The further you get before saving, the more there is to redo if the tab goes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It gives you the grid, not the answers. Every row still needs a human to decide what this product is and type it. For a short list that is fine. The grid is easy. The list is long. That is where people stop.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The bulk editor gives you the grid. The typing is still yours&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio proposes a type for every product that has none, drafted against that product's own title, tags and variants, so the job becomes reviewing a list instead of filling one in. You approve or discard each one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-product-type-missing&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-type-missing_listing_mid" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The same rows, after
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fny2us43dpmelhfaje40n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fny2us43dpmelhfaje40n.png" alt="Same rows after typing a type and hitting Save —  raw `Auburn Stripe Iringa Baskets` endraw  now reads **Home Decor**." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same grid, same rows, after typing and saving. The store in these three screenshots is our own demo store: 19 of its 68 products had no type, 27.9%, and it is &lt;strong&gt;not&lt;/strong&gt; part of the 42-store denominator in the next section.&lt;/p&gt;

&lt;p&gt;Two things to check after a save, because the grid says it saved and stops there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go back to the Products list and sort by Product type ascending again. The rows you just filled should no longer be at the top. That is the check that the write actually landed.&lt;/li&gt;
&lt;li&gt;If you have automated collections with a Product type condition, look at their product counts. Filling in types moves products into collections, and that is usually what you wanted, but it should not be a surprise.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How often catalogues actually leave product type blank
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it was measured.&lt;/strong&gt; On 2026-09-05 we requested &lt;code&gt;/products.json?limit=250&lt;/code&gt; from 43 public storefront domains. That endpoint is anonymous: no login, no browser, nothing authenticated. Of the 43, one did not respond and was excluded, leaving &lt;strong&gt;42 stores and 5,937 products&lt;/strong&gt; counted. A product counts as blank when its &lt;code&gt;product_type&lt;/code&gt; is missing or empty once surrounding whitespace is trimmed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whether it looks like you.&lt;/strong&gt; These 43 domains were collected because they publish machine-readable storefront metadata (UCP / &lt;code&gt;agents.md&lt;/code&gt;), which skews the group toward newer and more technically tidy stores. For this particular question that pushes in the conservative direction: if anything, a group like this should have &lt;em&gt;fewer&lt;/em&gt; blanks than a random store, not more. This is not a random sample and it is not the web. It is the same batch of public storefronts we used for our other catalogue measurement, not a second independent test of the same thing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stores read&lt;/td&gt;
&lt;td&gt;42 of 43 (1 excluded, no response)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Products counted&lt;/td&gt;
&lt;td&gt;5,937&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Products with no product type&lt;/td&gt;
&lt;td&gt;2,054&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blank rate, by product&lt;/td&gt;
&lt;td&gt;34.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blank rate, median store&lt;/td&gt;
&lt;td&gt;13.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stores with nothing typed&lt;/td&gt;
&lt;td&gt;9 of 42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stores with some typed and some not&lt;/td&gt;
&lt;td&gt;24 of 42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stores with everything typed&lt;/td&gt;
&lt;td&gt;9 of 42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stores whose catalogue hit the 250-product read cap&lt;/td&gt;
&lt;td&gt;16 of 42&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The two rates are not interchangeable.&lt;/strong&gt; 34.6% is per product, so large catalogues dominate it. 13.5% is the median store, so every store counts once. If you want "how much of the world's product data is untyped", use the first. If you want "what does a typical store look like", use the second. Quoting one as if it were the other is the mistake this table exists to prevent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the numbers do not cover.&lt;/strong&gt; 16 of the 42 stores hit the 250-product cap on that endpoint, and those 16 account for 67% of the counted products, so for two thirds of the products here we read the front of the catalogue rather than all of it. And &lt;code&gt;/products.json&lt;/code&gt; does not return the newer taxonomy Category at all, so this measures blank &lt;em&gt;product type&lt;/em&gt; only. It is not evidence that these products are unclassified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The row that matters most is the middle one.&lt;/strong&gt; 24 of 42 stores had some products typed and some not. Somebody was doing this properly. Then a supplier feed, a migration or a busy month landed a batch of untyped products in the middle of the catalogue. That is the shape the bulk editor is built for, and it is also the shape that is hardest to notice, because the field looks filled in everywhere you happen to look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five ways this goes wrong in practice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Near-duplicate types.&lt;/strong&gt; &lt;code&gt;T-Shirt&lt;/code&gt;, &lt;code&gt;T-shirt&lt;/code&gt;, &lt;code&gt;Tshirt&lt;/code&gt; and &lt;code&gt;Tees&lt;/code&gt; are four different product types as far as the store is concerned, and nothing warns you. The cost surfaces later, when a collection condition matches three of the four. Copy an existing value exactly instead of retyping it from memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typing the category into the type column.&lt;/strong&gt; They sit next to each other in the grid, and the taxonomy values are tempting because they sound official. Copying taxonomy strings into your user-defined field gives you a second, worse copy of a field you already have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Types that are really tags.&lt;/strong&gt; If a value describes an attribute rather than what the product is (&lt;code&gt;Sale&lt;/code&gt;, &lt;code&gt;New&lt;/code&gt;, &lt;code&gt;Summer&lt;/code&gt;, &lt;code&gt;Gift&lt;/code&gt;), it belongs in Tags. Product type is one label per product, so spending it on a temporary attribute means the product has no durable answer to "what is this".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Too many types.&lt;/strong&gt; A type that fits one product is just a longer title. If you are creating a type per product, the useful level is one step up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assuming the fix propagated.&lt;/strong&gt; Filling product type does not backfill Category, does not touch tags, and does not rebuild anything you exported earlier. A downstream system that took a copy of your catalogue yesterday still has the blanks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three catalogue shapes where this shows up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The supplier feed.&lt;/strong&gt; Products arrive from a dropshipper or a supplier sheet where the type column was never mapped. They land in one contiguous block, which is the easy case: sorted list, bulk editor, one type down the block, done in a pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The migration.&lt;/strong&gt; A store moves from another platform. The importer maps titles, prices and images cleanly, but has nowhere to put a field the old platform did not have. The result is a catalogue split at the move date, typed on one side and blank on the other. Sorting ascending makes that boundary visible immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The catalogue that outgrew the habit.&lt;/strong&gt; The early products were typed by hand by whoever built the store. Later, adding a product became something done in a hurry, and this field is the first thing a hurried person skips, because nothing on the product page asks for it any more. This is the 24-of-42 shape above: some products typed, some not, in the same store.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Where is the Product type field in the new Shopify admin?&lt;/strong&gt;&lt;br&gt;
It is not on the product page. When we read the full text of a product page in the current admin on 2026-09-05, including inside its shadow roots, "Product type" did not appear. The field still exists on the product and still has an input box in the bulk editor at &lt;code&gt;/bulk?resource_name=Product&amp;amp;edit=product_type&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Product type the same as Category?&lt;/strong&gt;&lt;br&gt;
No. Category is Shopify's standard taxonomy, chosen from a fixed tree and shared across all stores. Product type is your own user-defined label. Setting one does not set the other, and a product can have either, both, or neither.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I edit product type in bulk without touching a CSV?&lt;/strong&gt;&lt;br&gt;
Yes. That is what the bulk editor URL above does. It edits one field across many products in place, which avoids the main risk of the CSV route, where a re-import writes every column in the file rather than the one you intended to change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I find every product with no product type?&lt;/strong&gt;&lt;br&gt;
Sort the Products list by Product type, ascending. Empty values sort first, so the blanks collect at the top. No export needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I have to fill product type in at all?&lt;/strong&gt;&lt;br&gt;
Only if something in your store reads it: automated collection conditions, theme filtering, exports, or your own reporting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many product types should a catalogue have?&lt;/strong&gt;&lt;br&gt;
Few enough that each one groups several products. A type matching a single product groups nothing. We have not measured this, so treat it as a rule of thumb, not a benchmark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is a blank product type the same as an uncategorised product?&lt;/strong&gt;&lt;br&gt;
No, and our own measurement cannot tell you about the second one. The &lt;code&gt;/products.json&lt;/code&gt; endpoint we read does not return the taxonomy Category, so all we can say is that the user-defined product type was empty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will filling in product types change my collections?&lt;/strong&gt;&lt;br&gt;
It can, if you have automated collections whose conditions use Product type. Products that suddenly match a condition will join that collection. Check the product counts on those collections after a bulk save so the change is something you saw rather than something you discovered later.&lt;/p&gt;

&lt;h2&gt;
  
  
  If the grid is the part that stops you
&lt;/h2&gt;

&lt;p&gt;The bulk editor solves the access problem: the field has an input box again, and you can reach a screenful of products at a time. It does not solve the volume problem. Every row is still a decision, and the decision does not get faster with repetition.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One screen of the grid is quick. The rest of the catalogue is why people stop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio is an AI store operator: it reads the catalogue you already have, proposes the change per product, and waits for you to approve it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-product-type-missing&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-type-missing_listing_end" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not ready to install anything? Run the &lt;a href="https://arvio.a.xyz/free?from=devto-shopify-product-type-missing&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-type-missing_free4" rel="noopener noreferrer"&gt;free store audit&lt;/a&gt; on your storefront. It needs no account. It does not look at product type — it checks the storefront side: speed, SEO, mobile, checkout, conversion, and how ready the store is for AI shopping surfaces.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Product page field check and the 43-domain catalogue read were both done on 2026-09-05.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/shopify-product-type-missing?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-type-missing_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-product-type-missing&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-type-missing_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Product description examples, and what 8,019 live products actually look like</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:08:35 +0000</pubDate>
      <link>https://dev.to/arvio/product-description-examples-and-what-8019-live-products-actually-look-like-5dcb</link>
      <guid>https://dev.to/arvio/product-description-examples-and-what-8019-live-products-actually-look-like-5dcb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/product-description-examples-shopify?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=product-description-examples-shopify_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/product-description-examples-shopify&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every article about product descriptions shows you an example, and almost none of them say where it came from. Usually somebody wrote it for the article. That demonstrates a style perfectly well, but it tells you nothing about what a description on a real store looks like, or how yours compares.&lt;/p&gt;

&lt;p&gt;So we read some. Using the public catalogue endpoint that most Shopify storefronts expose, we read 225 live storefronts and measured the description on all 8,019 products they returned. The most common problem was not the one we went looking for. Empty descriptions turned out to be fairly rare, at 389 of 8,019. What is everywhere is repetition: 1,613 products, one in five, carry a description that another product in the same store carries word for word.&lt;/p&gt;

&lt;p&gt;One thing to be straight about, since the first paragraph makes an issue of it: the worked example further down is written for this article too, and it is invented on purpose, because holding up a real store's page as the bad one is not something we are willing to do. What the 8,019 products give you is the measurement, which is to say how common each failure actually is, and therefore which one is worth your afternoon.&lt;/p&gt;

&lt;p&gt;This applies to any catalogue with more products than patience, on Shopify or anywhere else. The measurement happens to come from Shopify storefronts, because that is the platform where the catalogue is usually readable without asking anyone's permission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1,613 of 8,019 products (20.1%) repeat another product's description word for word&lt;/strong&gt; in the same store, ignoring case and whitespace. This showed up on 111 of the 225 storefronts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1,265 (15.8%) run under 200 characters&lt;/strong&gt;, and 389 (4.9%) are completely empty. The empty ones are counted inside the 1,265.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The empty page is not the main event.&lt;/strong&gt; 184 of the 225 storefronts had no empty description anywhere, and only 3 had nothing written on any product.&lt;/li&gt;
&lt;li&gt;Length is not quality. The median description ran to &lt;strong&gt;701 characters&lt;/strong&gt;, and a long paragraph of adjectives is worse than 200 characters of specifics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A description is doing three jobs at once&lt;/strong&gt;, and the fill-in template below separates them: what the thing is, who it suits, and the specifics a buyer would otherwise have to email you about.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The descriptions at the end of the product list are the ones that never get written&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio reads your live catalogue, tells you which products have an empty or one-line description, and drafts each one against that product's own title, type, tags and variants. Nothing is saved until you approve it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=product-description-examples-shopify&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=product-description-examples-shopify_listing_top" rel="noopener noreferrer"&gt;See Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Check your own catalogue first
&lt;/h2&gt;

&lt;p&gt;Before copying anyone's example, look at what you already have. Most Shopify storefronts publish their catalogue as JSON, with no login and no app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-store.com/products.json?limit=250
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script runs the same two tests, in the same order, that produced the numbers below. Two differences are worth knowing before you compare: we stopped at 50 products per store and this walks your whole catalogue, and our sample is 225 stores while yours is one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# save as descriptions.py, then: python3 descriptions.py your-store.com
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;

&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                            &lt;span class="c1"&gt;# 250 at a time, until the catalogue runs out
&lt;/span&gt;    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://%s/products.json?limit=250&amp;amp;page=%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;                       &lt;span class="c1"&gt;# length test: strip the tags, decode the
&lt;/span&gt;    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;[^&amp;gt;]+&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# entities a browser would have
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;nbsp;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;amp;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;#39;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"'"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;quot;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;# rendered, then collapse the
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\s+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;       &lt;span class="c1"&gt;# whitespace and count
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;                        &lt;span class="c1"&gt;# duplicate test: the same, then lowercased
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;lengths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# empty descriptions are counted once as empty, not as copies of each other
&lt;/span&gt;&lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products read   &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lengths&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empty           &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lengths&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;under 200 chars &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lengths&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;same as another &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
                             &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;median length   &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lengths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lengths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lengths&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  fix (empty):&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  fix (copy): &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  fix (thin): &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last loop prints the handles, labelled by which problem they have, which is your work queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we measured
&lt;/h2&gt;

&lt;p&gt;Two tests, run over every product we could read. Length is characters after tags and whitespace are stripped. Duplication is that same text lowercased, then compared against every other product in the same store.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Products&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Same text as another product in the same store&lt;/td&gt;
&lt;td&gt;1,613&lt;/td&gt;
&lt;td&gt;20.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Under 200 characters&lt;/td&gt;
&lt;td&gt;1,265&lt;/td&gt;
&lt;td&gt;15.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description completely empty&lt;/td&gt;
&lt;td&gt;389&lt;/td&gt;
&lt;td&gt;4.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first row is scored on a different test from the other two, so a product can land in more than one of them. Inside the second row, 389 are empty and 876 have somewhere between 1 and 199 characters. Read those as two separate problems wearing the same symptom: pages that were never written, and pages where somebody typed one sentence and moved on.&lt;/p&gt;

&lt;p&gt;The store-level view is what changed our mind about which problem matters:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;At the store level&lt;/th&gt;
&lt;th&gt;Stores&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Have at least one description repeated on another product&lt;/td&gt;
&lt;td&gt;111 of 225&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Have no empty description anywhere&lt;/td&gt;
&lt;td&gt;184 of 225&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Have nothing written on any product&lt;/td&gt;
&lt;td&gt;3 of 225&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We expected the empty page to be the story. It is not. Four out of five of these stores have written something on everything they sell, and only 3 of 225 had left the whole catalogue blank. Meanwhile half of them have at least one description sitting on two products at once, which is a different kind of failure: not a missing page, a page that was filled in by copying the one above it.&lt;/p&gt;

&lt;p&gt;Here is the length spread, for context rather than as a target:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Chars&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shortest quarter, below&lt;/td&gt;
&lt;td&gt;347&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median&lt;/td&gt;
&lt;td&gt;701&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Longest quarter, above&lt;/td&gt;
&lt;td&gt;1,285&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Longest single product&lt;/td&gt;
&lt;td&gt;23,335&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What this sample does not tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;We read up to 50 products per store.&lt;/strong&gt; 126 of the 225 storefronts hit that ceiling, and those stores account for 79% of the products counted. So the shares above describe the front of a catalogue, not the whole of one. If the unwritten pages cluster at the end of the product list, which is where they usually collect, this measurement misses them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The stores chose themselves.&lt;/strong&gt; They come from a public list of merchants who posted their own storefront URL asking strangers for a review. That is not a random draw from Shopify, and these stores skew newer and smaller.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;That self-selection runs one way.&lt;/strong&gt; Somebody who asks the internet to critique their store is paying attention to it. Whatever these numbers say about sloppy descriptions, the wider population is unlikely to be tidier.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The template
&lt;/h2&gt;

&lt;p&gt;Copy this. It is deliberately short, because the failure modes we measured are absence, thinness and repetition, and no amount of styling advice fixes any of the three.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;[One sentence: what the thing is, in the words a buyer would use.]

[Two or three sentences: who it suits and what for. Name the situation.
"Fits a 13-inch laptop and a water bottle" beats "spacious and versatile".]

Details
&lt;span class="p"&gt;-&lt;/span&gt; [Material / composition]
&lt;span class="p"&gt;-&lt;/span&gt; [Dimensions or size, with units]
&lt;span class="p"&gt;-&lt;/span&gt; [What is included in the box]
&lt;span class="p"&gt;-&lt;/span&gt; [Care, compatibility, or anything a buyer would otherwise email to ask]

[One sentence: the thing that would make somebody return it. Say it
plainly. This is the line that stops the return, and it is the line
most descriptions leave out.]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first sentence is doing most of the work, because on a phone the description often sits collapsed behind a tap and that line is all anyone sees. The bullet block underneath is where the specifics live, and specifics are exactly what a buyer cannot get from the photo.&lt;/p&gt;

&lt;p&gt;The bullets are also what makes the template survive being reused. The prose at the top can be similar across a product family without much harm. The moment the dimensions, the contents and the care line are copied across as well, you have two pages that a buyer cannot tell apart, which is the shape 1,613 of the products we read are in.&lt;/p&gt;

&lt;p&gt;The last line looks like it is talking people out of buying, and it is. Naming who the product is wrong for costs you a few sales you would have refunded anyway, and it is what makes the four lines above it believable.&lt;/p&gt;

&lt;p&gt;None of this is a keyword slot. The search side of a product page lives in different fields, which we wrote about separately in &lt;a href="https://arvio.a.xyz/blog/shopify-missing-meta-descriptions?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=product-description-examples-shopify_blog2" rel="noopener noreferrer"&gt;Shopify meta descriptions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same product, written twice
&lt;/h2&gt;

&lt;p&gt;Both of these are written for this article. The product is invented so that no real store is being held up as a bad example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Beautiful handmade ceramic mug. Perfect for coffee lovers. Order yours today!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A 350 ml stoneware mug, thrown and glazed by hand, so no two are the
same shade.

It is heavier than a standard mug, which is the point if you drink
coffee slowly and hate a cold last mouthful. The handle takes two
fingers, so it sits differently in the hand than a standard mug.

Details
- Stoneware, food-safe reactive glaze
- 350 ml, 9 cm tall, 8.5 cm across the rim
- 460 g empty, roughly a third heavier than a thin-walled mug
- Dishwasher and microwave safe
- Sold singly, not as a set

If you want a matched set of six, this is the wrong mug. Colour varies
between firings, so two of these will rarely be an exact match.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second version is longer, but that is a side effect. What actually changed is that six questions got answered: capacity, weight, dimensions, care, whether it is a set, and whether the colour is consistent. The first version answers none of them and asks for the order anyway.&lt;/p&gt;

&lt;p&gt;Notice what happens if you paste that second version onto the same mug in a different glaze. Five of the six answers still hold, one of them is now wrong, and the page has stopped being about the product in the photo. That is how a description ends up on two products at once, and it rarely happens on purpose.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You now have the template. The catalogue is the hard part&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio takes the same shape you just filled in by hand and applies it product by product, using each product's own data instead of a generic paragraph. You review every draft and can undo any of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=product-description-examples-shopify&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=product-description-examples-shopify_listing_mid" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where the work actually is
&lt;/h2&gt;

&lt;p&gt;Writing one good description is a pleasant afternoon's work. Writing them until the product list runs out is a different activity, and that gap is where both the empty pages and the copied ones come from. A copied description is what an empty one looks like after somebody decided the page could not ship blank.&lt;/p&gt;

&lt;p&gt;Three ways through it, in the order most stores try them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One at a time in the admin.&lt;/strong&gt; Workable while the product list still fits on a screen or two. Open the product, write, save, back to the list, and repeat. The cost is all in the switching, and duplicating an existing product to save that cost is exactly what puts the same paragraph on two pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A CSV round trip.&lt;/strong&gt; Export, edit &lt;code&gt;Body (HTML)&lt;/code&gt; in a spreadsheet, import with overwrite. Faster for a large batch, and it is the route where the mistakes are expensive: a column present in the file but left blank will write an empty value over your live one. Delete the columns you are not changing before you import. A spreadsheet is also the fastest place to spot repetition, because sorting on the description column stacks the identical ones next to each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bulk editor.&lt;/strong&gt; Shopify's bulk editor shows one field across many products at once, which suits this job better than the product page does. We covered how to open it and which fields it can reach in &lt;a href="https://arvio.a.xyz/blog/bulk-edit-product-descriptions-shopify?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=product-description-examples-shopify_blog4" rel="noopener noreferrer"&gt;bulk editing product descriptions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If the thin or repeated descriptions all arrived on the same day, they probably came in together from a supplier feed or an import. That has its own pattern and its own fix, which we measured in &lt;a href="https://arvio.a.xyz/blog/shopify-fix-catalog-after-import?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=product-description-examples-shopify_blog5" rel="noopener noreferrer"&gt;cleaning up after an import&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Which products should I fix first?
&lt;/h3&gt;

&lt;p&gt;Empty ones, then the repeated ones, then the thin ones, and inside each group start with whatever you actually sell. The script prints handles in catalogue order, so it is worth re-sorting that list against your own sales before you begin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it a problem if two products share the same description?
&lt;/h3&gt;

&lt;p&gt;It was the most common thing we found: 1,613 of 8,019 products, on 111 of 225 storefronts. The cost is not a penalty handed down by anybody, it is that two pages a buyer cannot tell apart force them to work out the difference themselves, and some of them will not bother. The fix is usually small. Keep the shared paragraph, replace the specifics underneath it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long should a product description be?
&lt;/h3&gt;

&lt;p&gt;There is no target. In what we read, half of the products sat between 347 and 1,285 characters, with a median of 701, and the longest single description ran to 23,335. That spread is context, not a benchmark. A 200-character description that answers the size question beats an 800-character one that does not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a description on every product?
&lt;/h3&gt;

&lt;p&gt;Search engines and shopping surfaces read the description field, so an empty one is a page with less to work with. The stronger argument is simpler: a product page with no description asks a visitor to guess, and some of them will guess wrong and return it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I write the description or let a model draft it?
&lt;/h3&gt;

&lt;p&gt;A model drafting against a product's own title, type, tags and variants gives you something to edit, which beats an empty box. A model writing from the product name alone will invent specifics. The difference is what you feed it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about alt text on the product images?
&lt;/h3&gt;

&lt;p&gt;Worth doing, and a separate job, and the script above cannot audit it. In a different and much smaller read, 41 storefronts on 4 September 2026, the public catalogue endpoint listed 391 product images and returned no alt value on any of them. Read that as a fact about what the endpoint hands back, not as a count of stores that left the field blank, since the admin holds alt text the endpoint does not publish. It is also not the sample described above, so the two should not be added together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sample.&lt;/strong&gt; 225 live Shopify storefronts, read on 5 September 2026, taken from a public list of stores whose owners had posted their own URL on the Shopify Community's Store Feedback board. Everything here came from endpoints any browser can fetch. Store names, domains and product names are deliberately not published.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The funnel.&lt;/strong&gt; 230 stores on the list, 225 answered, 5 did not and are in no denominator. We requested up to 50 products from each and read 8,019 in total. 126 stores returned the full 50, which is the ceiling we set, and those stores hold 79% of the products counted, so treat every share above as describing the front of a catalogue rather than all of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Definitions.&lt;/strong&gt; Length is the character count after HTML tags are stripped and whitespace is collapsed. Duplication is that same text lowercased and compared with every other product in the same store, so it is a separate test from length and a long description can still be a copy. Both are the definitions the script above uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whether it looks like you.&lt;/strong&gt; Stores that post their URL asking for feedback skew newer and smaller, and the list was built from stores still trading at the time, so anything that had already gone dark is missing. It is a convenience list, not a random draw from Shopify, and it leans one way we should say out loud: a merchant who invites strangers to critique their store is more likely than average to have tidied it first.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One page is a pleasant afternoon. The rest of the catalogue is why people give up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio is an AI store operator: it reads the catalogue you already have, proposes the change per product, and waits for you to approve it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=product-description-examples-shopify&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=product-description-examples-shopify_listing_end" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Not ready to install anything?&lt;/strong&gt; Run the &lt;a href="https://arvio.a.xyz/free?from=devto-product-description-examples-shopify&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=product-description-examples-shopify_free7" rel="noopener noreferrer"&gt;free store audit&lt;/a&gt; on your storefront. It needs no account, and it reports empty and thin descriptions alongside the rest of your catalogue's data.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/product-description-examples-shopify?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=product-description-examples-shopify_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/product-description-examples-shopify&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=product-description-examples-shopify_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Sold out products still showing on Shopify: which of the two problems you have</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Sat, 05 Sep 2026 01:31:00 +0000</pubDate>
      <link>https://dev.to/arvio/sold-out-products-still-showing-on-shopify-which-of-the-two-problems-you-have-2la3</link>
      <guid>https://dev.to/arvio/sold-out-products-still-showing-on-shopify-which-of-the-two-problems-you-have-2la3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/shopify-sold-out-products-still-showing?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-sold-out-products-still-showing&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Published September 3, 2026 by &lt;a href="https://apps.shopify.com/partners/adot-inc?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_listing1" rel="noopener noreferrer"&gt;Adot Technologies Inc&lt;/a&gt;, the team behind &lt;a href="https://apps.shopify.com/arvio?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_listing2" rel="noopener noreferrer"&gt;Arvio: AI Store Operator&lt;/a&gt;. We read the public catalogue and the default collection listing on 30 live Shopify storefronts on September 3, 2026. Every measurement of those storefronts below comes from the 20 that returned a complete list; where this article gives numbers about the sampling frame itself — how many candidate domains there were, how many were dropped — those describe the frame, not the 20.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two people type this phrase, and they have opposite problems.&lt;/p&gt;

&lt;p&gt;One has a product sitting in a box in the back room, and the storefront says sold out. The other has products that really are gone, and they are still on the collection page, above the things a shopper could actually buy.&lt;/p&gt;

&lt;p&gt;The fixes have nothing in common, so the first job is telling them apart. That takes one request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;If something you have in stock reads as sold out, look at locations before you look at your theme.&lt;/strong&gt; Shopify's rule is inventory-level based, and an item can sit at a location that does not fulfil online orders while your online store still calls it out of stock.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If they really are sold out, Shopify does not remove them from your collections for you.&lt;/strong&gt; On the 20 stores where we could read the whole list, every sold-out product was still in &lt;code&gt;/collections/all&lt;/code&gt;: 211 of 211.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;42 of those sold-out products sat in the first 24 positions&lt;/strong&gt; of the default collection, and 9 of the 20 stores had at least one that high. The earliest sat in slot 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catalogue size is the one thing that separated these stores — as a direction, not a rate.&lt;/strong&gt; The 16 stores under 100 products had a median of 0.0% sold-out products on the shelf. The four stores over 100 had a median of 28.2% — four stores, so read that as a direction, not a rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify documents the fix, and documents the way it silently fails&lt;/strong&gt;: an automated collection condition on inventory, which does nothing at all if the collection is set to match any condition instead of all.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your collection page is showing customers things they cannot buy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finding them costs nothing — the script further down does it, and you should run it either way. Deciding what each one should say afterwards is the part that takes an afternoon: Arvio reads your catalogue's text fields, drafts the changes and applies nothing you have not approved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-sold-out-products-still-showing&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_listing_top" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The check that tells you which problem you have
&lt;/h2&gt;

&lt;p&gt;Your store publishes its catalogue as JSON, to anyone, with no app and no login. Open this in a browser tab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://yourstore.com/collections/all/products.json?limit=250
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns &lt;strong&gt;250 products at most&lt;/strong&gt;. If your catalogue is bigger, add &lt;code&gt;&amp;amp;page=2&lt;/code&gt;, &lt;code&gt;&amp;amp;page=3&lt;/code&gt; and so on until a page comes back empty — this is the same ceiling that made us throw out ten of our own thirty reads, and a truncated list looks exactly like a clean one.&lt;/p&gt;

&lt;p&gt;Search that page for &lt;code&gt;"available":false&lt;/code&gt;. Then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A product you believe is in stock comes back with &lt;code&gt;available: false&lt;/code&gt; on every variant&lt;/strong&gt; → problem 1. It is a stock or location setting, not a display bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Products you know are sold out are present in the list at all&lt;/strong&gt; → problem 2. They are still on your shelf, and the list order is the order shoppers scroll.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same request, piped through python3, prints the slot number and handle of each one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, time, urllib.request
store = 'yourstore.com'          # &amp;lt;- your domain
slot = 0
for page in range(1, 21):        # 20 pages = 5,000 products
    url = 'https://%s/collections/all/products.json?limit=250&amp;amp;page=%d' % (store, page)
    req = urllib.request.Request(url, headers={'User-Agent': 'shelf-check/1.0'})
    products = json.load(urllib.request.urlopen(req))['products']
    if not products:
        break
    for p in products:
        slot += 1
        v = p.get('variants') or []
        if v and all(x.get('available') is False for x in v):
            print(slot, p['handle'])
    time.sleep(1)
print('read %d listed products' % slot)
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last line is the check on the check: if it reports fewer products than your collection holds, the pages ran out early and everything above it is a partial answer.&lt;/p&gt;

&lt;p&gt;Slot 1 to 24 is roughly a first page: we could not find a documented platform default, and the first collection page on eight of these stores came back holding anywhere from 5 to 42 products, so treat 24 as our cut rather than as your theme's. That is the same read we ran against 30 storefronts; the numbers below come from the 20 whose lists came back complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: it says sold out and you are sure you have stock
&lt;/h2&gt;

&lt;p&gt;Shopify's definition is narrow, and both halves of it carry weight: &lt;em&gt;"An item is out of stock when inventory is tracked and the inventory level is at zero or below."&lt;/em&gt; (&lt;a href="https://help.shopify.com/en/manual/products/inventory/setup/selling-when-out-of-stock" rel="noopener noreferrer"&gt;Selling out of stock products&lt;/a&gt;) Two conditions, and both have to hold. So there are three places this goes wrong, in the order to check them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inventory tracking is on, and the number really is zero.&lt;/strong&gt; The storefront is reporting your data correctly. Adjust the quantity, or let it sell anyway: the &lt;strong&gt;Continue selling when out of stock&lt;/strong&gt; setting lives in the Inventory section of a product or variant, and Shopify only offers it when the &lt;strong&gt;Inventory tracked&lt;/strong&gt; setting is on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The stock is at a location that does not fulfil online orders.&lt;/strong&gt; This is the one that wastes afternoons, and Shopify (&lt;a href="https://help.shopify.com/en/manual/products/inventory/setup/selling-when-out-of-stock" rel="noopener noreferrer"&gt;Selling out of stock products&lt;/a&gt;) states the behaviour plainly: if an item is at a location that fulfils online orders with an inventory level of zero or below, and at another location that has stock but does not fulfil online orders, &lt;em&gt;"then that item is still displayed as out of stock to customers on your online store."&lt;/em&gt; The quantity in your admin looks fine. The storefront is not wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A dropshipping app location is missing shipping rates.&lt;/strong&gt; Shopify's own note (&lt;a href="https://help.shopify.com/en/manual/products/inventory/setup/selling-when-out-of-stock" rel="noopener noreferrer"&gt;Selling out of stock products&lt;/a&gt;) points here when products show out of stock while inventory is available: &lt;em&gt;"If you're using a dropshipping app and your products display as out of stock even though inventory is available, then your app location might be missing shipping rates."&lt;/em&gt; Check the app's location before you touch the theme.&lt;/p&gt;

&lt;p&gt;For the location case above, Shopify also states the fix in one sentence: &lt;em&gt;"You need to add stock to your location that fulfills online orders to allow customers to purchase that product."&lt;/em&gt; That is the whole repair — it is a stock move, not a theme change, and no setting in your theme will substitute for it.&lt;/p&gt;

&lt;p&gt;None of those three? Then the product is genuinely unavailable, and you are in problem 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: they really are sold out, and they are still on the page
&lt;/h2&gt;

&lt;p&gt;Here is what live stores do, rather than what the setting descriptions imply.&lt;/p&gt;

&lt;p&gt;We read two public endpoints on each store: the catalogue at &lt;code&gt;/products.json&lt;/code&gt; and the default collection at &lt;code&gt;/collections/all&lt;/code&gt;. Both cap out at 250 rows, so a store whose list hits the cap has products we never saw, and we dropped those stores. That leaves 20 stores where both lists came back complete.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Stores&lt;/th&gt;
&lt;th&gt;Products listed&lt;/th&gt;
&lt;th&gt;Sold out&lt;/th&gt;
&lt;th&gt;Still listed in the collection&lt;/th&gt;
&lt;th&gt;In the first 24 slots&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Complete reads&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;920&lt;/td&gt;
&lt;td&gt;211&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;211&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hit the 250-row cap&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;excluded&lt;/td&gt;
&lt;td&gt;excluded&lt;/td&gt;
&lt;td&gt;excluded&lt;/td&gt;
&lt;td&gt;excluded&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Every sold-out product was still on the shelf — all 211 of them.&lt;/strong&gt; Across 20 stores, not one had been taken out of the default collection. Read the shape of that evidence before you carry it: only 10 of the 20 had any sold-out product to hide, so the other 10 neither support this nor contradict it, and two of those 10 account for 166 of the 211.&lt;/p&gt;

&lt;p&gt;The totals hide the spread. The median store has 2.2% of its collection taken up by things nobody can buy, which is a rounding error. The worst share in the sample was &lt;strong&gt;82 of 156 listed products sold out&lt;/strong&gt; — 52.6%, more than half the shelf; the biggest pile by count was 84 sold-out products in a catalogue of 176. Ten of the 20 stores had any sold-out product listed at all, and in 9 of them a shopper meets one before scrolling: across those nine the earliest sat in slot 1 and the median first hit was slot 3. In the tenth, the first one sat in slot 31, past the first screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Whether this looks like you
&lt;/h3&gt;

&lt;p&gt;Sixteen of these 20 stores carry fewer than 100 products, and their median sold-out share is &lt;strong&gt;0.0%&lt;/strong&gt;. We cannot see why from outside — a small catalogue may simply never have had much sitting in it. The four stores with 100 or more products have a median of &lt;strong&gt;28.2%&lt;/strong&gt; — and that median describes none of them. Their four values are 4.6%, 8.7%, 47.7% and 52.6%: two stores with a little and two with half the shelf, with nothing in between. The median sits in the gap.&lt;/p&gt;

&lt;p&gt;Four stores is not a rate you should plan against, and 100 products is a cut point we chose after looking at the data, not before. So we moved it — with one thing said plainly: cutting at 75 selects the same four stores as cutting at 100, so that is not a second test. The cuts that do change the grouping are 50 (six stores, median 8.1%) and 150 (two stores, median 50.1%). The smaller side has a median of 0.0% at every one of them, and the direction holds every time; what moves is how much you can say about the larger side, which never gets above six stores. What that says is where to look: if your catalogue is small, run the command above and you will probably close this tab. If you carry hundreds of products, assume the shelf has gone stale until you have checked it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Shopify's help page says, and what we read
&lt;/h3&gt;

&lt;p&gt;Shopify's own documentation describes the default listing this way: &lt;em&gt;"By default, your store comes with a collection that displays all your in stock products at the URL your-store.myshopify.com/collections/all."&lt;/em&gt; (&lt;a href="https://help.shopify.com/en/manual/products/inventory/setup/hide-out-of-stock" rel="noopener noreferrer"&gt;Hiding out of stock products&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Read that sentence as a merchant and you would not expect what we found. Both are on the record: that sentence is what the help page says, and the JSON above is what the endpoint returned. The Catalog collection is a Shopify-managed default, and it may mean something looser than a shopper would read into it. If that sentence is your model of your own storefront, open the JSON and look.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to hide them, the way Shopify documents
&lt;/h2&gt;

&lt;p&gt;The mechanism is a collection condition, and it is the same three steps for every collection:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Turn on inventory tracking&lt;/strong&gt; for the products. Without it there is no inventory level to test, and the condition below cannot fire.&lt;/li&gt;
&lt;li&gt;In the collection, &lt;strong&gt;add the condition &lt;code&gt;Inventory stock&lt;/code&gt; is greater than &lt;code&gt;0&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set the collection to match all conditions.&lt;/strong&gt; This is the step that fails silently: Shopify's &lt;a href="https://help.shopify.com/en/manual/products/inventory/setup/hide-out-of-stock" rel="noopener noreferrer"&gt;page on hiding out of stock products&lt;/a&gt; carries the note &lt;em&gt;"This solution for hiding out of stock products won't work if you set Products must match to any condition."&lt;/em&gt; No error, no warning, and the collection keeps listing everything.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two more things from that same page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The default Catalog page needs its own treatment.&lt;/strong&gt; You take control of &lt;code&gt;/collections/all&lt;/code&gt; by overriding it with your own collection, then set that collection's automated conditions the same way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It reverses itself when stock comes back.&lt;/strong&gt; &lt;em&gt;"Any out of stock products display in your collections again after you receive inventory."&lt;/em&gt; This is a display rule, not a decision about the product.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you would rather it happen on a schedule than as a condition, Shopify Flow has a &lt;strong&gt;Publish and unpublish products based on inventory level&lt;/strong&gt; template, which acts on the product rather than on the collection. That is a different outcome: a collection condition hides the product from a list, while unpublishing takes the product page down. Whether you want the second one is a real question with SEO consequences on both sides, and we worked through it separately in &lt;a href="https://arvio.a.xyz/blog/shopify-unpublish-out-of-stock-products?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_blog4" rel="noopener noreferrer"&gt;unpublishing out-of-stock products on Shopify&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Turning the condition on takes two minutes. Working through the catalogue behind it does not.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio goes through your products one at a time. What it changes, what it leaves alone and what it got wrong on a real store, we wrote up separately in &lt;a href="https://arvio.a.xyz/blog/what-arvio-actually-does?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_blog5" rel="noopener noreferrer"&gt;what Arvio actually does&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-sold-out-products-still-showing&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_listing_mid" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What this article does not know
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;We have no admin access to any of these stores.&lt;/strong&gt; So we cannot tell you whether a store shows sold-out products by accident or on purpose, and plenty of stores do it on purpose: a sold-out product page keeps the URL that already ranks, and it keeps collecting demand signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We read the JSON list, not the rendered page.&lt;/strong&gt; A theme can grey out or visually hide a product that is still present in the list. Everything above is about what the collection publishes, not about what pixels a shopper sees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ten of the 30 stores hit the 250-row cap&lt;/strong&gt; on the collection list and are excluded from every number here. Bigger catalogues are exactly where this problem concentrates, so what we measured is the smaller half of our own sample.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"First 24 slots" is our cut, not a platform constant.&lt;/strong&gt; We could not find a documented Shopify default; &lt;code&gt;shopify.dev&lt;/code&gt; only says a paginated list holds &lt;em&gt;"between 1 and 250"&lt;/em&gt; items. When we counted what the first collection page actually returned on eight of these storefronts we got 5, 12, 16, 16, 16, 20, 24 and 42 products. So read "the first 24" as the first two dozen positions in the listing — for some themes that is more than one screen, for others less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One read, one day.&lt;/strong&gt; A collection is a live object; a store that looked clean on September 3 may not be clean next week.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sample.&lt;/strong&gt; The frame is built from store URLs their owners posted publicly on the Shopify Community asking for feedback: 720 topics, 474 distinct store domains, of which 230 still served a catalogue on August 26, 2026. From those 230 we drew 60 at random with a recorded seed (20260826), then asked the first 35 in alphabetical order; 30 answered, and 20 of those returned complete lists. Only the 230 to 60 step is random — the 35 is a prefix of that draw, so the seed reproduces the 60, not the 35.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The biggest bias is survivorship.&lt;/strong&gt; Of the 474 candidate domains, 244 no longer served a catalogue at all. Stores that closed are missing from every number here, and a closed store is not a store that kept its shelf clean. The frame also skews newer and smaller than the Shopify population.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Endpoints, September 3, 2026.&lt;/strong&gt; Per store: one &lt;code&gt;GET /products.json?limit=250&lt;/code&gt; and one &lt;code&gt;GET /collections/all/products.json?limit=250&lt;/code&gt;, one request per second, with a user agent identifying our research crawler and carrying a contact address. No authentication, no app, nothing that is not public.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sold-out definition.&lt;/strong&gt; A product counts as sold out when it has variants and every variant reports &lt;code&gt;available: false&lt;/code&gt;. Products with some variants available are not counted. We never see inventory quantities — &lt;code&gt;available&lt;/code&gt; is what the storefront publishes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Position.&lt;/strong&gt; We take the collection order as returned and record the 1-based slot of each sold-out product. First screen is slots 1 to 24.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusion rule.&lt;/strong&gt; Both endpoints return at most 250 rows. Any store whose collection list came back at 250 rows is excluded from every denominator, because the sold-out products we found in its catalogue may simply be past the cut. That rule removes 10 of the 30 stores. It also removes an artefact: counted naively across all 30 stores, 163 sold-out products look as though they were kept out of the collection, and on the 20 complete reads that number is zero, store by store.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does Shopify hide sold-out products automatically?&lt;/strong&gt;&lt;br&gt;
No. Nothing in the default setup removes a sold-out product from a collection. Hiding it is a collection condition you add, an app, or a Flow workflow — and on the 20 stores we could read completely, every sold-out product we found was still listed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does a product show as sold out when I have stock?&lt;/strong&gt;&lt;br&gt;
Because Shopify calls an item out of stock when inventory is tracked and the level is zero or below at the locations that fulfil your online orders. Stock at a location that does not fulfil online orders does not count, and Shopify says such an item is still displayed as out of stock on your online store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I hide sold-out products or unpublish them?&lt;/strong&gt;&lt;br&gt;
Different outcomes. Hiding removes the product from a collection while its product page stays up. Unpublishing takes the page down, along with whatever that URL had earned. We went through that trade-off in &lt;a href="https://arvio.a.xyz/blog/shopify-unpublish-out-of-stock-products?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_blog7" rel="noopener noreferrer"&gt;unpublishing out-of-stock products&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will the condition hide products I want to keep visible?&lt;/strong&gt;&lt;br&gt;
It hides everything at or below zero inventory, including things you are happy to keep listed for preorder. If you want those to stay purchasable, turn on &lt;strong&gt;Continue selling when out of stock&lt;/strong&gt; for them, which keeps them buyable rather than merely visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I know it worked?&lt;/strong&gt;&lt;br&gt;
Re-run the command at the top of this article. It reads the same list a shopper's browser reads, so if a handle still prints, the product is still on the shelf.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does any of this affect Google?&lt;/strong&gt;&lt;br&gt;
Only indirectly, and not through this file. What Google reads is your product pages and structured data, and a sold-out product publishes an availability value there too. That is a separate check, and we measured it separately in &lt;a href="https://arvio.a.xyz/blog/shopify-products-missing-from-google-shopping?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_blog8" rel="noopener noreferrer"&gt;products missing from Google Shopping&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arvio: AI Store Operator — &lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-sold-out-products-still-showing&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_listing_end" rel="noopener noreferrer"&gt;install it on the Shopify App Store&lt;/a&gt;.&lt;/strong&gt; The condition is one setting; the catalogue behind it is the work.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/shopify-sold-out-products-still-showing?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-sold-out-products-still-showing&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-sold-out-products-still-showing_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Shopify structured data errors: which ones actually occur</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Sat, 05 Sep 2026 01:29:20 +0000</pubDate>
      <link>https://dev.to/arvio/shopify-structured-data-errors-which-ones-actually-occur-7ea</link>
      <guid>https://dev.to/arvio/shopify-structured-data-errors-which-ones-actually-occur-7ea</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/shopify-structured-data-errors?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-structured-data-errors_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-structured-data-errors&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Published September 3, 2026 by &lt;a href="https://apps.shopify.com/partners/adot-inc?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-structured-data-errors_listing1" rel="noopener noreferrer"&gt;Adot Technologies Inc&lt;/a&gt;, the team behind &lt;a href="https://apps.shopify.com/arvio?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-structured-data-errors_listing2" rel="noopener noreferrer"&gt;Arvio: AI Store Operator&lt;/a&gt;. The counts below come from parsing every JSON-LD block on 84 product pages across 30 live Shopify storefronts on September 3, 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The obvious way to attack a structured data problem is product by product: audit the catalogue, fix the bad ones, keep auditing.&lt;/p&gt;

&lt;p&gt;We went to check that. Seven things that can go wrong, 84 product pages, 30 distinct storefronts. Four of the seven never happened once. Two of the three that did were store-wide: every store that published no product markup published none on every page we read, and every store with an unparsable block had one on every page. The third — a product node with no image — varied from page to page inside the same store.&lt;/p&gt;

&lt;p&gt;So checking gets cheaper. If your markup is broken, one product page will tell you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;23 of the 30 stores had none of the three failures&lt;/strong&gt;, on every page we read — two or three pages each, from a convenience frame of stores that asked a public forum for feedback, not a random sample of Shopify.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seven pages published no product markup at all&lt;/strong&gt; — four of them carried no JSON-LD whatsoever, three carried only a business block. All seven came from three stores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Six pages carried a JSON-LD block that will not parse.&lt;/strong&gt; All six still had valid product markup: the broken block sits beside the product node, not on top of it. Two stores, every page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four errors people are warned about did not occur once&lt;/strong&gt;: no page carried competing product nodes, no price was non-numeric, no availability value was unrecognised, and every variant-group node carried its variants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The one place the page and the catalogue disagreed, the page was the pessimistic one.&lt;/strong&gt; Prices agreed on all 77 pages that had a product node. Availability disagreed on exactly one page: the markup said out of stock for a product the catalogue said you could buy.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Structured data breaks by store, not by product&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Structured data comes from your theme. The fields it publishes come from your catalogue — and that half is what Arvio reads, drafts, and hands back for your approval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-structured-data-errors&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-structured-data-errors_listing_top" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Check your own store in one request
&lt;/h2&gt;

&lt;p&gt;Structured data is in the HTML your storefront serves anyone. Pick any product page and run this. It prints one line per JSON-LD block, says whether the block parses, and names the types inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://yourstore.com/products/your-product-handle"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
| python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys,re,json
html = sys.stdin.read()
blocks = re.findall(r'&amp;lt;script[^&amp;gt;]*application/ld&lt;/span&gt;&lt;span class="se"&gt;\+&lt;/span&gt;&lt;span class="s2"&gt;json[^&amp;gt;]*&amp;gt;(.*?)&amp;lt;/script&amp;gt;', html, re.S)
print('%d JSON-LD block(s)' % len(blocks))
products = 0
for i, b in enumerate(blocks, 1):
    try:
        data = json.loads(b)
    except Exception as err:
        print('  block %d  DOES NOT PARSE  %s' % (i, err))
        continue
    nodes = data if isinstance(data, list) else [data]
    if isinstance(data, dict) and isinstance(data.get('@graph'), list):
        nodes = data['@graph']
    for n in nodes:
        if not isinstance(n, dict):
            continue
        t = n.get('@type')
        keys = sorted(k for k in n if not k.startswith('@'))
        print('  block %d  %-14s %s' % (i, t, ', '.join(keys)))
        if t not in ('Product', 'ProductGroup'):
            continue
        products += 1
        offers = n.get('offers') or []
        if isinstance(offers, dict):
            offers = [offers]
        for o in offers[:3]:
            if isinstance(o, dict):
                print('      offer %-14s price=%r low=%r high=%r availability=%r'
                      % (o.get('@type'), o.get('price'), o.get('lowPrice'),
                         o.get('highPrice'), o.get('availability')))
        if t == 'ProductGroup':
            print('      hasVariant: %d' % len(n.get('hasVariant') or []))
print('product nodes on this page: %d' % products)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six of the seven checks below are in that output. Whether a &lt;code&gt;Product&lt;/code&gt; or &lt;code&gt;ProductGroup&lt;/code&gt; type appears at all, and whether any block says &lt;code&gt;DOES NOT PARSE&lt;/code&gt;, are the first two lines you read. Whether &lt;code&gt;image&lt;/code&gt; is in the key list next to the product type is the third. The last line — &lt;code&gt;product nodes on this page&lt;/code&gt; — is the competing-nodes check: anything above 1 is two product nodes fighting over the same page. And the indented &lt;code&gt;offer&lt;/code&gt; lines are the format checks: &lt;code&gt;price&lt;/code&gt; should be a bare number, not &lt;code&gt;$24.00&lt;/code&gt;, and &lt;code&gt;availability&lt;/code&gt; should be one of the schema.org values. A &lt;code&gt;ProductGroup&lt;/code&gt; prints its &lt;code&gt;hasVariant&lt;/code&gt; count, and a group that publishes no variants prints 0. The seventh check, whether the markup agrees with your catalogue, needs a second request — to your own &lt;code&gt;/products.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One caveat on running it once: our sample is at most three product pages per store, and one store gave us only one. So when we say a failure was the same on every page of a store, that means the two or three pages we read, not the catalogue.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we tested, and what we found
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What we checked&lt;/th&gt;
&lt;th&gt;Pages&lt;/th&gt;
&lt;th&gt;Stores&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No product markup on the page at all&lt;/td&gt;
&lt;td&gt;7 of 84&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A JSON-LD block that will not parse&lt;/td&gt;
&lt;td&gt;6 of 84&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product node with no &lt;code&gt;image&lt;/code&gt; property&lt;/td&gt;
&lt;td&gt;5 of 77&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two or more competing product nodes&lt;/td&gt;
&lt;td&gt;0 of 84&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A price that is not a number&lt;/td&gt;
&lt;td&gt;0 of 77&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An availability value outside the vocabulary&lt;/td&gt;
&lt;td&gt;0 of 77&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variant group published without its variants&lt;/td&gt;
&lt;td&gt;0 of 27&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Of the 84 pages, 50 published a &lt;code&gt;Product&lt;/code&gt; node, 27 published a &lt;code&gt;ProductGroup&lt;/code&gt;, and 7 published neither. The typical page carries two JSON-LD blocks; 44 of the 84 do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three that occurred
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Product pages with no product markup
&lt;/h3&gt;

&lt;p&gt;Seven pages, three stores. One store served no JSON-LD at all on all three pages we read; a second served exactly one block on each of its three pages, describing the business rather than the product; the third had only one page in our sample, and that page carried no JSON-LD either.&lt;/p&gt;

&lt;p&gt;Those seven pages are not all the same failure. Four of the seven served &lt;strong&gt;no JSON-LD at all&lt;/strong&gt; — not a business block, not a breadcrumb, nothing. The other three served exactly one block describing the business, and nothing describing the thing for sale. Those three pages do have something a validator can approve of — an organisation block — and nothing at all describing the thing for sale. We did not run a validator against them, so what any specific tool reports is not something this survey can tell you.&lt;/p&gt;

&lt;p&gt;This looks like a theme-level property: in all three stores it held on every page we read. We did not record which theme or app each store runs, so we cannot say which of those explains it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What that costs, stated the only way we can state it.&lt;/strong&gt; We have no traffic data for these stores, so we cannot tell you what those seven pages lost. What we can tell you is what Google publishes as the rule. Its product structured data documentation says: &lt;em&gt;"You must include the required properties for your content to be eligible for display as a rich result."&lt;/em&gt; — and the required properties for a product snippet are &lt;em&gt;"Product snippets require either review or aggregateRating or offers"&lt;/em&gt;, with the page needing &lt;em&gt;"You must include one of the following properties: review aggregateRating offers"&lt;/em&gt;. A page with no product node has none of them. So the seven pages are not a style problem with an unknown cost: on the platform's own written rule they are outside the door, and the three that publish an organisation block instead are outside it in exactly the same way. What we cannot tell you — and nobody can from the outside — is how much traffic was on the other side of that door for these particular stores.&lt;/p&gt;

&lt;h3&gt;
  
  
  A JSON-LD block that will not parse
&lt;/h3&gt;

&lt;p&gt;Six pages, two stores, every page we read from each. On all six, the page still carried a valid product node.&lt;/p&gt;

&lt;p&gt;The combination matters more than either half. One store's pages carried four blocks each — a breadcrumb, a website block, the product, and one that would not parse. The other's carried three — a business block, the product, and the broken one. On all six pages exactly one block was malformed. A parser that stops at the first error reports the page as broken. A parser that reads block by block gets the product fine and discards only the block that failed. We have no Search Console for these stores, so which of those any given search engine does is outside what we measured.&lt;/p&gt;

&lt;p&gt;So the practical severity depends on which block broke. Losing a breadcrumb block costs you a breadcrumb. The reason to fix it anyway is what it says about the template: the same template renders the product block, and on both of these stores the broken block appeared on every page we read. We did not capture what was inside the broken blocks, so we cannot tell you what put them there.&lt;/p&gt;

&lt;h3&gt;
  
  
  A product node with no image
&lt;/h3&gt;

&lt;p&gt;Five pages, three stores. One store missed it on all three pages we read; two others on one page each.&lt;/p&gt;

&lt;p&gt;Three of those five products &lt;strong&gt;have a real photo in the catalogue&lt;/strong&gt; — main images that fetch fine at 1308 by 1308, 1308 by 1308 and 1000 by 1000 pixels. The photo is in the catalogue and fetches fine, and the machine-readable description of the product does not mention it. We did not render these pages, so what a shopper sees is outside what we measured. The other two products have no image in the catalogue either, which is a different problem with a different fix.&lt;/p&gt;

&lt;p&gt;"Add an image" is therefore the wrong instruction for three of these five: the image is already there, and the markup is what needs fixing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four that never occurred
&lt;/h2&gt;

&lt;p&gt;We picked these seven because each one is visible in the page's own markup, without a validator, a login or a guess about what any search engine does with it. In 84 pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two competing product nodes on one page.&lt;/strong&gt; Zero. This is the classic outcome of installing a schema app on a theme that already emits markup, and we did not find it once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A non-numeric price&lt;/strong&gt; — a currency symbol or a comma inside the price field. Zero, on all 77 pages with a product node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An unrecognised availability value.&lt;/strong&gt; Zero. Every page used a value from the standard vocabulary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A variant group published without its variants.&lt;/strong&gt; Zero, across all 27 pages that used the group shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the one check that needs the catalogue as well as the page: on all 77 pages with a product node, the price in the markup matched the store's own catalogue price for that product. Availability disagreed on exactly one page, where the markup said out of stock and the catalogue said the product was available — the direction that costs a shopper nothing on the page itself, and the wrong direction for anyone reading your page to decide whether to show it.&lt;/p&gt;

&lt;p&gt;Prices agreeing on 77 of 77 is not a compliment to anyone's diligence. That is what you would see if both sides were rendered from the same product record; we did not verify that mechanism, only that the two never disagreed. It is worth knowing precisely because so much advice assumes otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern that matters
&lt;/h2&gt;

&lt;p&gt;Twenty-three of the 30 stores had none of the three failures on any page. Of the seven stores that did fail, five failed on every page we read from that store. Both exceptions are the image check: two stores missed the image on one page out of the three we read.&lt;/p&gt;

&lt;p&gt;Structured data that comes from the theme rather than from the product would look exactly like this, and it has a practical consequence: &lt;strong&gt;auditing product by product is the wrong unit of work.&lt;/strong&gt; Check one page. If it is clean on the two checks that come from the template — a product node exists, and every block parses — that is evidence about your template, and templates do not usually vary product by product. Our own evidence for that is narrow: every store in our sample was identical on both checks across the pages we read, and "the pages we read" is two or three per store. It is a reason to start with one page, not a reason to stop there. If it is broken, you are looking at a template, and fixing the template fixes the catalogue.&lt;/p&gt;

&lt;p&gt;The exception is the one thing that does vary per product: whether the fields on that product are filled in at all. That is a different survey, and it is linked below.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One page tells you whether your markup is broken. The catalogue behind it is the other job.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio goes product by product through your live catalogue, drafts what it would change, and applies nothing you have not approved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-structured-data-errors&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-structured-data-errors_listing_mid" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What to do about each one
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No product markup.&lt;/strong&gt; Check whether your theme is current and whether the product template has been customised. If an app owns your product page, the markup question belongs to that app. Re-run the command above afterwards: the &lt;code&gt;Product&lt;/code&gt; or &lt;code&gt;ProductGroup&lt;/code&gt; line either appears or it does not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A block that will not parse.&lt;/strong&gt; Find which block, then find the field. Unescaped quotes and line breaks in merchant-entered text are the usual cause, so look at the product or store fields the broken block is rendering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing image in the product node.&lt;/strong&gt; Check whether the product has an image at all. If it does, the markup is not rendering it and that is a template fix; if it does not, upload one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything else.&lt;/strong&gt; Four of the seven checks came back empty — competing product nodes on 0 of 84 pages, non-numeric prices and unrecognised availability values on 0 of 77, variant groups missing their variants on 0 of 27. If you are working from a list of structured-data errors to hunt down, these four can come off it until a validator on your own store says otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this article does not know
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;We have no Search Console for these stores.&lt;/strong&gt; Nothing here says whether any of these pages lost a rich result, a Shopping placement or an impression. We measured what the pages publish, and stopped there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We read three pages from 26 of the 30 stores&lt;/strong&gt;, two pages from two stores and one page from two more. That is enough to catch a store-wide property and not enough to estimate how many products in a catalogue are affected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We did not render the pages.&lt;/strong&gt; Markup injected by JavaScript after load is invisible to this method, and a page that looks empty here may not be empty to a renderer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field coverage is a different question.&lt;/strong&gt; What Shopify emits and which fields it fills — ratings, barcodes, shipping, returns — we measured separately on a larger sample in &lt;a href="https://arvio.a.xyz/blog/shopify-schema-markup?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-structured-data-errors_blog5" rel="noopener noreferrer"&gt;what 58 live stores actually serve as schema markup&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sample.&lt;/strong&gt; A public frame of 230 live Shopify storefronts, and the frame is public in the sense that matters: you can rebuild it. We read 24 pages of the Shopify Community's store-feedback board (&lt;code&gt;community.shopify.com/c/store-feedback/125&lt;/code&gt;) on August 26, 2026 — 720 topics — and took the hostnames linked in each topic's first post, the one written by the merchant asking for feedback; links in replies do not count. That produced 474 candidate hosts. Each was checked with a single &lt;code&gt;GET /products.json?limit=1&lt;/code&gt;, and 244 failed it — 118 domains that no longer resolve, 56 returning 404, 31 returning HTTP 402 (Shopify's unpaid freeze), 18 password-protected or no longer Shopify, and 21 more spread across 401, 409, 403, 308, 500, 429 and one dropped connection. The 230 that answered are the frame, which makes it a frame of stores that survived. &lt;code&gt;random.Random(20260826).sample()&lt;/code&gt; drew 60 of the 230; we then went alphabetically down that list of 60 and asked the first 35, of which 30 answered. The seed reproduces the 60, not the 35: the last step is a prefix, not a second draw. The frame skews newer and smaller than the Shopify population, and it is a frame of stores that are still trading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pages, September 3, 2026.&lt;/strong&gt; Up to three product pages per store, taken at even intervals through the store's own catalogue order rather than picked by us. 88 fetches, 84 HTTP 200 responses, one request per second, with a user agent identifying our research crawler and carrying a contact address.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parsing.&lt;/strong&gt; Every &lt;code&gt;application/ld+json&lt;/code&gt; block on the page, parsed independently. A block that raises on parse is counted as unparsable and does not disqualify the others. Nodes are read through &lt;code&gt;@graph&lt;/code&gt; where present, so a product nested in a graph counts as a product node.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The catalogue comparison.&lt;/strong&gt; For each page with a product node, the price and availability in the markup are compared with that product's current values in the same store's &lt;code&gt;/products.json&lt;/code&gt;. Both sides were read within the same run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusion rule.&lt;/strong&gt; The four fetches that did not return HTTP 200 are excluded from every denominator. No page is counted twice, and no store contributes more than three pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I check my Shopify structured data?&lt;/strong&gt;&lt;br&gt;
Run the command above on one product page, or paste the page URL into Google's Rich Results Test. The command tells you exactly what your server sends. Start with it, because in our sample the two theme-level failures were identical on every page of a store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Shopify generate product structured data automatically?&lt;/strong&gt;&lt;br&gt;
In our sample, 77 of 84 pages carried a product node. The seven that did not came from three stores. We did not record what those three stores run, so we cannot say why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is one broken JSON-LD block a serious problem?&lt;/strong&gt;&lt;br&gt;
Less serious than a validator makes it sound. On all six pages where we found one, the product markup was intact and parsed fine. Fix it because of what it says about the template, not because the product node is at risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My product has a photo but the markup has no image. Which do I fix?&lt;/strong&gt;&lt;br&gt;
The markup. Three of the five image-less product nodes we found belong to products with a perfectly good main image in the catalogue, so uploading another photo would change nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need to audit every product?&lt;/strong&gt;&lt;br&gt;
For two of the three, our data says no — with the sample size stated: no product markup and unparsable blocks were the same on every page we read of a store, every single time, and 23 of 30 stores were clean everywhere. That is two or three pages per store, not a catalogue. The image check is the one that changed from page to page even inside a store, so that is the one that still belongs to the product. Check one page per template first, then the products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arvio: AI Store Operator — &lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-structured-data-errors&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-structured-data-errors_listing_end" rel="noopener noreferrer"&gt;install it on the Shopify App Store&lt;/a&gt;.&lt;/strong&gt; The template is one fix; the fields behind it are the catalogue's job.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/shopify-structured-data-errors?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-structured-data-errors_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-structured-data-errors&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-structured-data-errors_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Product images not showing on Shopify: which of the three failures you have</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Sat, 05 Sep 2026 01:28:32 +0000</pubDate>
      <link>https://dev.to/arvio/product-images-not-showing-on-shopify-which-of-the-three-failures-you-have-3j77</link>
      <guid>https://dev.to/arvio/product-images-not-showing-on-shopify-which-of-the-three-failures-you-have-3j77</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/shopify-product-images-not-showing?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-product-images-not-showing&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Published September 3, 2026 by &lt;a href="https://apps.shopify.com/partners/adot-inc?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_listing1" rel="noopener noreferrer"&gt;Adot Technologies Inc&lt;/a&gt;, the team behind &lt;a href="https://apps.shopify.com/arvio?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_listing2" rel="noopener noreferrer"&gt;Arvio: AI Store Operator&lt;/a&gt;. We read the public catalogue of 30 live Shopify storefronts on September 3, 2026 and fetched a sample of their main image URLs directly. Every count below is from the 25 of those whose catalogue we read to the end; five stores hit our 1,000-product ceiling, so their totals are partial and are excluded rather than mixed in.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"Product images not showing" is three different problems wearing one sentence.&lt;/p&gt;

&lt;p&gt;The image file does not load, and the shopper gets a grey box. Or the product genuinely has no image, and the theme is showing a placeholder. Or — the common one nobody searches for by name — the shopper picks a colour and the photo does not change, because that variant has no image of its own.&lt;/p&gt;

&lt;p&gt;Three causes and three fixes. The one request below tells you which of them you are looking at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The image files themselves came back fine every time we asked for one.&lt;/strong&gt; We fetched up to five main image URLs per store — 119 across 25 stores — and all 119 returned HTTP 200. Read that for what it is: an anonymous request to the CDN succeeded. It says the file exists and is being served; it cannot tell you the image renders on the page, and five products per store is a spot check, not a survey of the file layer. If your image is not loading, the suspect list starts with your theme and your cache, not Shopify's CDN.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Products with no image at all are rare.&lt;/strong&gt; 38 of 3,689 products, and 35 of those 38 belong to one store. This is an import problem when it happens, not a background rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The variant layer is where it goes wrong at scale.&lt;/strong&gt; 2,442 products out of 3,689 have more than one variant, and 807 of them (33.0%) have no variant-level image at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is a per-store habit, not a random defect.&lt;/strong&gt; Six of the 25 stores had no variant images on any multi-variant product. Six others had them on every one. The median store sits at 20.5%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One script tells you which of the three you have.&lt;/strong&gt; It walks the same public catalogue JSON we read, page by page: whether the product has no image at all, or has variants and no image on any of them.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Two thirds of the products we read have variants. A third of those carry one photo for every option a shopper can pick.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The script above already finds them, at no cost to you — keep using it. What it cannot do is the rest of the catalogue it walks past: the titles, descriptions, metadata and alt text on those same products. Arvio reads that surface, drafts the changes, and applies nothing you have not approved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-product-images-not-showing&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_listing_top" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The one request that tells you which failure you have
&lt;/h2&gt;

&lt;p&gt;Your catalogue is public JSON. Open this in a browser tab, no app and no login:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://yourstore.com/products.json?limit=250
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That URL gives you the &lt;strong&gt;first 250 products only&lt;/strong&gt;. If your catalogue is bigger, &lt;code&gt;&amp;amp;page=2&lt;/code&gt;, &lt;code&gt;&amp;amp;page=3&lt;/code&gt; and so on give you the rest — a 400-product store needs two pages, and reading one of them and calling it a catalogue is the most common way to get a clean-looking wrong answer.&lt;/p&gt;

&lt;p&gt;Or get it as a task list. This prints one line per product that is missing an image somewhere — either the product itself has none, or it has variants and not one of them carries its own image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys, time, urllib.request
store = 'yourstore.com'          # &amp;lt;- your domain
seen = 0
for page in range(1, 21):        # 20 pages = 5,000 products
    url = 'https://%s/products.json?limit=250&amp;amp;page=%d' % (store, page)
    req = urllib.request.Request(url, headers={'User-Agent': 'catalogue-check/1.0'})
    products = json.load(urllib.request.urlopen(req))['products']
    if not products:
        break
    for p in products:
        seen += 1
        imgs = p.get('images') or []
        vs = p.get('variants') or []
        if not imgs:
            print('NO IMAGE AT ALL   ', p['handle'])
        elif len(vs) &amp;gt; 1 and not any(v.get('featured_image') for v in vs):
            print('NO VARIANT IMAGES ', p['handle'], '(%d variants)' % len(vs))
    time.sleep(1)
print('read %d products' % seen)
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last line is the one to read first. If it says fewer products than your admin does, the pages ran out early and the list above is incomplete — raise the page range and run it again.&lt;/p&gt;

&lt;p&gt;That is the same rule we ran across 30 storefronts — 25 of which we read to the end, and those 25 are the ones every count in this article comes from. If it prints nothing and shoppers still report grey boxes, you are in failure 1 below and the answer is not in your catalogue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 1: the image does not load
&lt;/h2&gt;

&lt;p&gt;This is the one people assume, and it was the one we could not reproduce.&lt;/p&gt;

&lt;p&gt;We took a sample of main image URLs straight from the catalogues — 119 of them across 25 stores — and requested each one directly, reading only the first kilobyte. Every single one came back HTTP 200 with an image content type. Not one 404, not one 403, not one timeout.&lt;/p&gt;

&lt;p&gt;That does not mean your image is fine. It means the file was there and serving every time we asked, so the failure sits somewhere between the file and the shopper's screen. We did not measure that stretch; the list below is not from our data, it is the order worth checking in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your theme's lazy-loading.&lt;/strong&gt; A blank slot that fills in when you scroll, or never fills in on a slow connection, is theme code, not a missing file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A cached page.&lt;/strong&gt; You replaced the image and the shopper has the old HTML. Hard-reload before you debug anything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A blocked request.&lt;/strong&gt; Ad blockers and corporate proxies eat CDN requests. Try a different network before you rebuild your product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The URL you are checking is not the URL the page uses.&lt;/strong&gt; Themes rewrite image URLs to add size suffixes. Copy the broken image's URL out of the page, not out of your admin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The check that separates these from a real catalogue problem is the one at the top: if the handle does not print, the product has an image and the file will serve when requested directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 2: the product has no image at all
&lt;/h2&gt;

&lt;p&gt;Rare, and concentrated. 38 of 3,689 products across 25 stores had zero images — 1.0% — and 35 of them were in a single store, a catalogue of 176 products.&lt;/p&gt;

&lt;p&gt;That shape is worth reading. Thirty-five of the 38 sitting in one catalogue is not a background rate; it is one batch. Which batch, we cannot see. Twenty-one of the 25 stores had zero products with no image.&lt;/p&gt;

&lt;p&gt;If the command above prints a long list of &lt;code&gt;NO IMAGE AT ALL&lt;/code&gt; lines, treat it as an import artefact and go looking for the batch it came from. We wrote up that cleanup separately in &lt;a href="https://arvio.a.xyz/blog/shopify-fix-catalog-after-import?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_blog4" rel="noopener noreferrer"&gt;fixing a Shopify catalogue after a messy import&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 3: you pick a variant and the photo does not change
&lt;/h2&gt;

&lt;p&gt;Nobody files this one as a bug. On the page it does not look like one.&lt;/p&gt;

&lt;p&gt;Two thirds of the products we read have variants: 2,442 out of 3,689, or 66.2%. Among those multi-variant products, &lt;strong&gt;807 (33.0%) have no variant-level image at all.&lt;/strong&gt; Not a missing image on one colour: no image assigned to any variant of the product.&lt;/p&gt;

&lt;p&gt;What a shopper sees is one photo that does not respond. What the catalogue hands the theme is one photo for the whole product. Pick Green and there is no Green photo to hand over, because that variant carries none; a theme doing the standard fallback has nothing but the product's first image to show. On a mug that is a nuisance. On paint, apparel, or anything where the variant &lt;em&gt;is&lt;/em&gt; the thing being chosen, it is the whole decision they came to make.&lt;/p&gt;

&lt;p&gt;The distribution says this is a workflow, not an accident:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Multi-variant products with no variant image&lt;/th&gt;
&lt;th&gt;Stores&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All of them (100%)&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Some of them&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None of them (0%)&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The median store sits at 20.5%. The extreme case in our sample was a 151-product catalogue in which all 150 multi-variant products had no variant image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Shopify lets this happen:&lt;/strong&gt; a variant image is optional. When a variant has none, the theme falls back to the product's first image, which is a sensible default and a silent one. There is no warning, no validation error, and nothing in the admin that lists products where variants share a single photo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix, per product:&lt;/strong&gt; open the product, open each variant, and give it an image from the product's media. The check afterwards is the same script as before — a handle that stops printing is a product that is fixed. It is per variant, though: a product with 4 colours × 3 sizes has 12 variants, and the images are assigned one at a time. That is why a third of multi-variant products in the sample never got them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Finding the products is one request. Assigning images to a few hundred variants is the afternoon.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Assigning a photo to a variant is media work, and it stays yours — no tool in this article does it for you. Arvio works the text side of the same catalogue item by item. What it changes, what it leaves alone and what it got wrong on a real store, we wrote up separately in &lt;a href="https://arvio.a.xyz/blog/what-arvio-actually-does?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_blog5" rel="noopener noreferrer"&gt;what Arvio actually does&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-product-images-not-showing&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_listing_mid" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What about image size?
&lt;/h2&gt;

&lt;p&gt;Separate problem, separate audience. Google's product data specification asks for images of at least 500 x 500 pixels — and its own wording is that &lt;em&gt;"Enforcement of the new requirements will begin January 31, 2027"&lt;/em&gt;, so as of today it is a deadline, not a live rule. It is also about whether your products can serve in Shopping, not about whether a shopper sees a picture on your own store. In our sample 188 of the 3,651 products that have a main image (5.1%) carried one under 500 pixels on one side, 20 were under 250, and one was under 100. The denominator is 3,651 rather than 3,689 because 38 products have no main image to measure. We measured that requirement properly, against Google's own wording and enforcement date, in &lt;a href="https://arvio.a.xyz/blog/shopify-products-missing-from-google-shopping?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_blog7" rel="noopener noreferrer"&gt;why your products are missing from Google Shopping&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if your question is how many images a product should have in the first place, we counted that across a larger sample in &lt;a href="https://arvio.a.xyz/blog/shopify-how-many-product-images?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_blog8" rel="noopener noreferrer"&gt;how many product images Shopify stores actually use&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this article does not know
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;We never rendered a page.&lt;/strong&gt; Everything here is the JSON your storefront publishes. Theme bugs, lazy-loading, CDN behaviour and caching are exactly where failure 1 lives, and they are invisible to this method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;featured_image&lt;/code&gt; is what we read.&lt;/strong&gt; A theme can associate images with variants by other means — metafields, naming conventions, alt-text matching — and a store doing that would look like a failure here and behave correctly for shoppers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Five of the 30 stores hit the 1,000-product read cap&lt;/strong&gt; and are excluded from every number above. Large catalogues are where variant counts get big, so the honest version is that we measured the smaller half.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We have no admin access.&lt;/strong&gt; Nothing here says a merchant forgot anything. A store can have good reasons to show one photo per product.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One read, one day.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sample.&lt;/strong&gt; The frame is built from store URLs their owners posted publicly on the Shopify Community asking for feedback: 720 topics, 474 distinct store domains, of which 230 still served a catalogue on August 26, 2026. From those 230 we drew 60 at random with a recorded seed (20260826), then read the first 31 in alphabetical order; 30 answered, and 25 of those returned a complete catalogue. Only the 230 → 60 step is random.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The biggest bias is survivorship.&lt;/strong&gt; Of the 474 candidate domains, 244 did not return a public catalogue when we asked — 118 domains that no longer resolve, 56 returning 404, 31 returning HTTP 402 (Shopify's unpaid freeze), 18 password-protected or no longer running Shopify, and 21 other refusals. Most of those look like stores that are gone, but "we could not read it" is not the same finding as "it closed", and the password-protected ones are plainly still someone's store. What is fair to say is that stores which have stopped serving a catalogue are missing from every number here, and they are unlikely to be the stores that got their variant images right. The frame also skews newer and smaller than the Shopify population.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Catalogue read, September 3, 2026.&lt;/strong&gt; Per store, &lt;code&gt;GET /products.json?limit=250&lt;/code&gt; for up to four pages or 1,000 products, one request per second, with a user agent identifying our research crawler and carrying a contact address.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What was counted.&lt;/strong&gt; A product has no image when its &lt;code&gt;images&lt;/code&gt; array is empty. A product counts as multi-variant when it has more than one variant, and as having no variant image when none of those variants carries a &lt;code&gt;featured_image&lt;/code&gt;. Main image dimensions come from the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; the endpoint reports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image fetches.&lt;/strong&gt; For a subset of products we requested the main image URL directly and read the first kilobyte, recording status, content type and content length. 119 fetches, 119 HTTP 200 responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusion rule.&lt;/strong&gt; Any store whose catalogue read reached the 1,000-product cap is excluded from every denominator, because the products past the cap could change any share we quote. That removes 5 of the 30 stores.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why does my Shopify product image not change when I select a variant?&lt;/strong&gt;&lt;br&gt;
Because that variant has no image assigned, so the theme falls back to the product's first image. It is the default state, not a fault: 33.0% of the multi-variant products we read across 25 stores were in it. Assign an image to each variant in the product editor, then re-run the check above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are Shopify product images broken or is it my theme?&lt;/strong&gt;&lt;br&gt;
Request the image URL directly. In our sample all 119 main images we fetched returned HTTP 200, so a file that serves on request and does not appear on the page points at the theme, the cache, or the network — not at the catalogue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is my product showing a placeholder image?&lt;/strong&gt;&lt;br&gt;
The product has no images at all. That was 1.0% of the products we read, and 35 of the 38 cases were in one store, which is the signature of an import rather than of forgetting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need an image for every variant?&lt;/strong&gt;&lt;br&gt;
Not technically — Shopify treats it as optional, which is why so many stores skip it. It matters when the variant is what the shopper is choosing. If your options are sizes of the same object, one photo is honest. If they are colours or materials, one photo answers the wrong question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does a missing variant image affect Google?&lt;/strong&gt;&lt;br&gt;
Not through this route. What Google reads is the product page and its structured data, and the image requirement it enforces is about size, not about variants. That is the article linked above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I check this without running a script?&lt;/strong&gt;&lt;br&gt;
Yes: open &lt;code&gt;https://yourstore.com/products.json?limit=250&lt;/code&gt; and search for &lt;code&gt;"featured_image": null&lt;/code&gt;. The script only saves you the scrolling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arvio: AI Store Operator — &lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-product-images-not-showing&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_listing_end" rel="noopener noreferrer"&gt;install it on the Shopify App Store&lt;/a&gt;.&lt;/strong&gt; Finding the products is a single request; putting an image on the variants behind them is the part that needs a hand.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/shopify-product-images-not-showing?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-product-images-not-showing&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-product-images-not-showing_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Shopify redirects: the one rule that decides whether yours will work</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Sat, 05 Sep 2026 01:27:58 +0000</pubDate>
      <link>https://dev.to/arvio/shopify-redirects-the-one-rule-that-decides-whether-yours-will-work-40me</link>
      <guid>https://dev.to/arvio/shopify-redirects-the-one-rule-that-decides-whether-yours-will-work-40me</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/shopify-redirects?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-redirects_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-redirects&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Published September 3, 2026 by &lt;a href="https://apps.shopify.com/partners/adot-inc?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-redirects_listing1" rel="noopener noreferrer"&gt;Adot Technologies Inc&lt;/a&gt;, the team behind &lt;a href="https://apps.shopify.com/arvio?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-redirects_listing2" rel="noopener noreferrer"&gt;Arvio: AI Store Operator&lt;/a&gt;. The measurements below come from requesting nine URL shapes against 20 live Shopify storefronts on September 3, 2026, and from Shopify's own redirect documentation read the same day.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is a particular kind of Shopify thread that never gets resolved. Someone set up a URL redirect, saved it, tested it, and nothing happened. No error. The old URL still loads. They check the spelling four times, clear the cache, ask the forum, and the thread dies.&lt;/p&gt;

&lt;p&gt;In these threads there is one rule worth ruling out first, and it is stated plainly in Shopify's documentation without ever being made prominent:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can redirect only from broken URLs. Broken URLs display error messages, such as Page not found or 404, on a page or in the page title. If the URL still loads a valid webpage, then the URL redirect won't work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read it twice, because it inverts the mental model most people bring. A redirect on Shopify is not a rule that overrides a page. It is a rule that fills a hole. &lt;strong&gt;If there is no hole, the rule never fires.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Which turns the useful question into one you can answer with a single request: &lt;strong&gt;is the URL I am pointing at actually broken?&lt;/strong&gt; That turns out to be answerable, and the answer is not obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redirects only fire on URLs that already 404.&lt;/strong&gt; A redirect whose source URL still loads does nothing — that is Shopify's own rule, and its troubleshooting list says as much: "Ensure the Redirect from URL doesn't have an active page." It is the first thing to check when a redirect appears to do nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The URL shapes people most want to redirect are not broken.&lt;/strong&gt; An uppercase handle, a trailing slash, and a junk &lt;code&gt;?variant=&lt;/code&gt; parameter all returned 200 on all 20 stores we tested. They are duplicates, not holes, so a redirect will not touch them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is handling those duplicates is the canonical tag, not a redirect&lt;/strong&gt; — correctly, on 19 of the 20 stores. On the twentieth, the one product we read pointed its canonical at the store's home page instead — a real defect, and not one a redirect would fix. We read one product per store, so that is one page on one storefront, not a measured rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The genuinely broken shapes are consistent.&lt;/strong&gt; A missing product, a missing collection, and a valid handle requested under the wrong resource type each returned 404 on 20 of 20 stores. Those are the ones redirects are for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/collections/all/products/&amp;lt;handle&amp;gt;&lt;/code&gt; is the one Shopify collapses for you&lt;/strong&gt; — 16 of 20 stores redirected it to the plain product URL. The other 4 served the product at the collection path, where the canonical still pointed at the plain product URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify's own page contradicts itself on international subfolders&lt;/strong&gt; — it says both that redirects do not automatically apply to market subfolders and that they do. Test yours rather than trusting either sentence.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Redirects fix URLs. They do not fix the page you land on&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A redirect decides where the visitor lands. What is on that page comes from your catalogue — descriptions, product type, metadata, images — and that half is what Arvio reads, drafts, and hands back for your approval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-redirects&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-redirects_listing_top" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Which URLs on your store are actually broken?
&lt;/h2&gt;

&lt;p&gt;We took the first product and the first collection each of 20 live Shopify storefronts lists, and requested nine URL shapes for each, following redirects and recording where each one landed. Every shape is one a merchant genuinely runs into — from a bad paste, an old link, a mistyped handle, or a theme that builds URLs a second way.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;URL shape&lt;/th&gt;
&lt;th&gt;200&lt;/th&gt;
&lt;th&gt;404&lt;/th&gt;
&lt;th&gt;Path changed&lt;/th&gt;
&lt;th&gt;Redirectable?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/products/&amp;lt;handle&amp;gt;&lt;/code&gt; (control)&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/collections/all/products/&amp;lt;handle&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No — it is a fixed Shopify path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/products/&amp;lt;handle&amp;gt;/&lt;/code&gt; (trailing slash)&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;No — all 20 serve it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/products/&amp;lt;HANDLE&amp;gt;&lt;/code&gt; (uppercase)&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No — all 20 serve it&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/products/&amp;lt;handle&amp;gt;?variant=1&lt;/code&gt; (junk variant)&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No — all 20 serve it&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/products/&amp;lt;handle&amp;gt;-typo&lt;/code&gt; (missing product)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/collections/&amp;lt;handle&amp;gt;&lt;/code&gt; (control)&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/collections/&amp;lt;handle&amp;gt;-typo&lt;/code&gt; (missing collection)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/pages/&amp;lt;product-handle&amp;gt;&lt;/code&gt; (wrong resource type)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;"Path changed" counts the final URL landing on a different path than the one requested&lt;/strong&gt;, which is not the same as a store running a redirect for that shape. The trailing-slash row is where the difference matters: 14 of the 20 stores serve the slashed URL exactly as it stands, and the other 6 are the host-normalising stores described under &lt;em&gt;Where this stops&lt;/em&gt; — their single 301 drops the slash while it moves the host. No store in the sample ran a slash-stripping redirect of its own.&lt;/p&gt;

&lt;p&gt;Two things fall out of that table, plus one detail about the redirects themselves.&lt;/p&gt;

&lt;p&gt;Every redirect in the run was a permanent one: 70 redirect hops in total, all 70 returning &lt;strong&gt;301&lt;/strong&gt;, including the 22 that changed the path rather than just normalising the host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 404s are clean.&lt;/strong&gt; Every one of the three broken shapes returned an honest 404 on every store — no soft 404s, no "helpful" 200 landing pages, no silent bounce to the home page. That is good news, and it is the precondition for a redirect to work at all. If your platform returned 200 for missing products, redirects would never fire.&lt;/p&gt;

&lt;p&gt;The other half of the table is the part that frustrates people: none of the duplicate shapes is broken, so none of them is within reach of a redirect. An uppercase handle returns the product. A trailing slash is served as-is on 14 of the 20 stores, and stripped with a 301 on the other 6 — either way the URL never 404s. A junk variant parameter returns the product on all 20. All three are the same page on a second URL, and because none of them is broken, &lt;strong&gt;there is nothing a redirect can do about them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is where a lot of frustration comes from. Someone sees the same product at two URLs, reasons "I'll redirect one to the other," writes the redirect, saves it, and the second URL keeps loading. It always will.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is handling the duplicates instead
&lt;/h3&gt;

&lt;p&gt;The canonical tag. On 19 of the 20 stores, the product page emits a &lt;code&gt;&amp;lt;link rel="canonical"&amp;gt;&lt;/code&gt; pointing at the plain &lt;code&gt;/products/&amp;lt;handle&amp;gt;&lt;/code&gt; URL — and it keeps pointing there on the uppercase version, the trailing-slash version, the collection-path version, and the version with the junk variant parameter. That is the mechanism telling search engines which URL is the real one, and on 19 of the 20 storefronts we read it was already in place — we changed nothing on those stores, and we did not look at how their themes were configured.&lt;/p&gt;

&lt;p&gt;One store out of 20 is the exception, and it is worth naming as a shape rather than a domain: on the one product we read there, the canonical points at the store's &lt;strong&gt;home page&lt;/strong&gt; — and it does that on all five shapes where we captured the tag. We read one product per store, so whether the rest of that catalogue does the same is something we did not test. What is safe to say is that a redirect is the wrong tool for it: that product URL loads fine, so there is no hole for a redirect to fill.&lt;/p&gt;

&lt;p&gt;If you want to check yours, it is one request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yourstore.com/products/some-handle | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'rel="canonical"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want to see that product's own URL come back. If you see the home page, or a different product, that is the thing to fix — before you spend an afternoon on redirects that were never the problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  The one duplicate Shopify does collapse
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/collections/all/products/&amp;lt;handle&amp;gt;&lt;/code&gt; — the URL a theme produces when a product link is built from inside a collection — behaved differently from the rest. &lt;strong&gt;16 of the 20 stores redirected it to the plain &lt;code&gt;/products/&amp;lt;handle&amp;gt;&lt;/code&gt; URL.&lt;/strong&gt; The remaining 4 served the product at the collection path, with the canonical still pointing at the plain product URL.&lt;/p&gt;

&lt;p&gt;You cannot manage that either way from the redirect tool. Shopify's documentation lists the paths that are off limits: "You can't redirect URLs that use fixed Shopify paths: /products, /collections, /collections/all." The platform handles this shape itself, and where it does not, the canonical covers it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you change a product handle
&lt;/h2&gt;

&lt;p&gt;A handle change is what people reach for redirects for, and the same rule decides it: if the old URL returns 404 after the change, it is redirectable; if anything still serves it, no redirect you write will fire.&lt;/p&gt;

&lt;p&gt;We did not test this. We created no redirects and changed nothing on any store, and Shopify's redirect page does not mention product handles at all — so do the one check that settles it on your own store. Change the handle on a low-stakes product, then &lt;code&gt;curl -I&lt;/code&gt; the old URL. A 404 means you can create the redirect; a 200 means a redirect there would be a no-op no matter how you write it.&lt;/p&gt;

&lt;p&gt;The same goes for a change that arrives through an import or an app rather than by hand: the question is never how the change was made, it is what the old URL returns now.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you delete or hide something
&lt;/h2&gt;

&lt;p&gt;Deleting a product produces a 404 at its URL. That is correct behaviour, and it is not automatically a problem — though &lt;a href="https://arvio.a.xyz/blog/shopify-archive-vs-delete-products?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-redirects_blog4" rel="noopener noreferrer"&gt;whether to archive or delete in the first place&lt;/a&gt; is a decision worth making before you get here: a product that never had inbound links or traffic does not need a redirect, and an internet full of redirects to vaguely-related pages is not better than an honest 404.&lt;/p&gt;

&lt;p&gt;Redirect when the old URL had something worth preserving — a link from somewhere, &lt;a href="https://arvio.a.xyz/blog/shopify-products-not-showing-on-google?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-redirects_blog5" rel="noopener noreferrer"&gt;a page in the index&lt;/a&gt;, a run of traffic. Send it to the nearest genuinely equivalent page, which is usually the collection the product sat in rather than a random other product.&lt;/p&gt;

&lt;p&gt;Hiding rather than deleting has a wrinkle that is easy to get bitten by, and it is documented: hiding a collection — or setting a product to Draft, or removing it from all sales channels — makes its URL return 404, which makes it eligible for a redirect — and then, per Shopify, "If you later change the collection's Online store visibility back to Visible, the URL redirect is automatically deleted because redirects only work for URLs that return 404 errors."&lt;/p&gt;

&lt;p&gt;So a seasonal collection you hide each summer and redirect elsewhere will lose that redirect the moment you make it visible again. The redirect does not sit dormant. It is deleted, and you recreate it next season.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating them, one at a time and in bulk
&lt;/h2&gt;

&lt;p&gt;From the admin, redirects live under &lt;strong&gt;Content → Menus → URL redirects&lt;/strong&gt;. Creating one is: &lt;strong&gt;Create URL redirect&lt;/strong&gt;, fill in &lt;strong&gt;Redirect from&lt;/strong&gt; with the old (broken) URL and &lt;strong&gt;Redirect to&lt;/strong&gt; with the destination, then &lt;strong&gt;Save redirect&lt;/strong&gt;. To send something to the home page, the destination is just &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Destinations inside your own store should be relative (&lt;code&gt;/collections/t-shirts&lt;/code&gt;); a full URL is for sending traffic off your domain.&lt;/p&gt;

&lt;p&gt;For a batch — a migration, a bulk delete, a taxonomy change — the same page has &lt;strong&gt;Import&lt;/strong&gt;, which takes a CSV. Shopify provides a sample redirect CSV to use as a template; fill that in, then Import → Add File → Upload file → review → Import redirects. Shopify's stated ceilings are &lt;strong&gt;100,000&lt;/strong&gt; redirects on standard plans and &lt;strong&gt;20,000,000&lt;/strong&gt; on Plus, so the limit is not what will stop you.&lt;/p&gt;

&lt;p&gt;What does stop people is further down the same page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Some prefixes cannot be the source of a redirect at all.&lt;/strong&gt; Shopify's list is &lt;code&gt;/apps&lt;/code&gt;, &lt;code&gt;/application&lt;/code&gt;, &lt;code&gt;/cart&lt;/code&gt;, &lt;code&gt;/carts&lt;/code&gt;, &lt;code&gt;/orders&lt;/code&gt;, &lt;code&gt;/services&lt;/code&gt;, &lt;code&gt;/shop&lt;/code&gt;, plus the fixed paths above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collection tag-filter URLs are not redirectable&lt;/strong&gt; — a URL like &lt;code&gt;/collections/name/tag-name&lt;/code&gt; is considered valid even when no product carries that tag, so it never 404s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.html&lt;/code&gt; endings cannot be redirected to the same URL without &lt;code&gt;.html&lt;/code&gt;.&lt;/strong&gt; Shopify treats those as the same URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query strings are unreliable as redirect sources.&lt;/strong&gt; The documentation says URLs containing them "might not work as expected."&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The source really is a 404 and the redirect still does nothing
&lt;/h3&gt;

&lt;p&gt;The rule at the top of this article answers one half of "my redirect isn't working" — the half where the old URL still loads. If your&lt;br&gt;
source URL genuinely returns 404 and the redirect still does not fire, Shopify's own troubleshooting list has three more steps, and they&lt;br&gt;
are the whole list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"Review both Redirect from and Redirect to URLs for any typos."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Ensure that the Redirect from URL isn't a fixed path or contains a prefix that you can't redirect."&lt;/em&gt; — this is the trap our own
measurement walked into: &lt;code&gt;/collections/all/products/&amp;lt;handle&amp;gt;&lt;/code&gt; 404s for nobody, and 16 of the 20 stores we read redirect it themselves.
A fixed path is not yours to redirect even when it looks broken.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Clear your browser's cache and test the redirect again or try on another device."&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cache cuts the other way too, and it is the reason a redirect you deleted can appear to still work: &lt;em&gt;"301 redirects are cached by&lt;br&gt;
browsers and search engines. If you remove a redirect after customers have accessed it, then the redirect might continue to work in&lt;br&gt;
their browsers until the cache expires."&lt;/em&gt; Test in a private window, or with &lt;code&gt;curl -sIL&lt;/code&gt;, before concluding anything from your own&lt;br&gt;
browser.&lt;/p&gt;

&lt;p&gt;One thing this list does &lt;strong&gt;not&lt;/strong&gt; include is waiting: Shopify's page says &lt;em&gt;"URL redirects start working immediately."&lt;/em&gt; If it did not&lt;br&gt;
work a minute after you saved it, more minutes are not the fix.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You have the redirects. Is the page they land on worth landing on?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A redirect moves a visitor to a page. What that page says comes from your catalogue — and Arvio reads your live catalogue, drafts the gaps, and applies nothing you have not approved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-redirects&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-redirects_listing_mid" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The documentation contradicts itself on international subfolders
&lt;/h2&gt;

&lt;p&gt;If you sell into market subfolders — &lt;code&gt;/en-ca&lt;/code&gt;, &lt;code&gt;/en-fr&lt;/code&gt; — you need to know whether one redirect covers all of them. Shopify's page answers it twice, in opposite directions, in two different sections. Both were on the page when we read it on September 3, 2026.&lt;/p&gt;

&lt;p&gt;In the considerations near the top:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;URL redirects don't automatically apply to all market subfolders. If you need to redirect specific locales (such as yourstore.com/en-ca/old-page to yourstore.com/en-fr/new-page), then you must create individual redirects for each subfolder.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And in the section headed &lt;em&gt;Redirects and subfolders&lt;/em&gt;, with a worked example that walks through a Canadian shopper being redirected inside &lt;code&gt;/en-ca&lt;/code&gt; from a single redirect created at the root:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Creating a URL redirect applies to all language or market subfolders set up using international sales tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Neither sentence is dated, and we have no way to tell which one is current. So settle it on your own store: &lt;strong&gt;create one redirect at the root, then request the subfolder URL and see where you land.&lt;/strong&gt; One &lt;code&gt;curl -I&lt;/code&gt; per market takes a minute; re-reading the page takes longer and still leaves you with two answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this stops
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;We did not create any redirects.&lt;/strong&gt; Everything in the table is a read of what these stores already serve. The redirect behaviour described in the second half comes from Shopify's documentation, not from our own write test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 301s above are what these stores did on the day.&lt;/strong&gt; Every hop we recorded was one, but we sampled 20 storefronts on one morning — that is not a statement about how Shopify behaves in general.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Six of the 20 stores redirect every request once at the host level&lt;/strong&gt; — the usual &lt;code&gt;www&lt;/code&gt;-to-apex or apex-to-&lt;code&gt;www&lt;/code&gt; normalisation. A hop that only moves the host is not counted as a redirect in the table, because counting it would have made all nine shapes look like they redirect. Those same six stores are the 6 in the trailing-slash row, and the reason they are there is worth being exact about: on that shape their host hop and the dropped slash are &lt;strong&gt;one 301&lt;/strong&gt;, not two, so the path genuinely changes — but no store in the sample ran a slash-stripping redirect of its own. On the collection-path shape the same six take two hops; the trailing slash costs them none.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20 stores is a small sample, and the frame behind it is biased toward stores that are still trading.&lt;/strong&gt; It was built from 474 publicly posted store URLs, of which 244 no longer served a public catalogue and were dropped before the draw — so this describes storefronts that are alive today, not Shopify as a whole. Nothing here describes Shopify Plus stores behind a CDN, where a rule at the edge can change any of this.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The sample.&lt;/strong&gt; A public frame of 230 live Shopify storefronts, built on August 26, 2026 from store URLs their owners posted publicly. Sixty hostnames were drawn from that frame with a recorded seed (&lt;code&gt;random.Random(20260826).sample&lt;/code&gt;), and this article reads the first 20 of those 60 in alphabetical order. The cut to 20 was ours, not the data's: nine shapes per store at one request per second is 180 requests, and we capped the survey there. So the draw is random and the prefix is not — alphabetical order has nothing to do with anything measured here, but it is a convenience cut and you should read the counts as 20 stores, not as a random 20 of the 60. All 20 answered, so no store dropped out at this step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The requests (September 3, 2026).&lt;/strong&gt; For each store we read &lt;code&gt;/products.json&lt;/code&gt; and &lt;code&gt;/collections.json&lt;/code&gt; and took the first product handle and the first collection handle each one returned, then issued one GET per URL shape at one request per second with a user agent identifying our research crawler and carrying a contact address. We followed redirects and recorded the full hop chain, the final URL, the final status code, and — on five of the nine shapes — the &lt;code&gt;&amp;lt;link rel="canonical"&amp;gt;&lt;/code&gt; href from the response body. On the other four we did not read the body at all, so their canonical count is zero because we did not look, not because the tag is absent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Redirected" means the path changed.&lt;/strong&gt; Six stores normalise host or scheme on every request. Counting those as redirects would have reported all nine shapes as redirecting on those stores, which is true and useless, so the table counts only redirects where the URL path differs from the one requested.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rerunning this on your own store takes one line per shape.&lt;/strong&gt; We have not published the raw hop chains — they name 20 real storefronts — but nothing here needs our copy: &lt;code&gt;curl -sIL -o /dev/null -w '%{http_code} %{url_effective}\n' "https://yourstore.com/products/your-handle/"&lt;/code&gt; prints the same two facts we recorded (where it landed, and with what status), and the nine shapes are listed in the table above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The uppercase and junk-variant shapes were fetched twice.&lt;/strong&gt; The first pass did not read the response body for those two, so we refetched both the same day purely to capture the canonical tag. That refetch is recorded separately from the first pass, and the canonical counts in this article come from it for those two shapes only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The documentation&lt;/strong&gt; was read on the same day. &lt;code&gt;help.shopify.com&lt;/code&gt; returns 403 to a plain scripted request, so the page was read through a normal logged-in browser session and the rendered text — 10,173 characters — was stored whole alongside the numbers. Every quoted sentence in this article is copied verbatim from that capture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusion rule.&lt;/strong&gt; A store enters the table only if all of &lt;code&gt;/products.json&lt;/code&gt;, &lt;code&gt;/collections.json&lt;/code&gt; and the control product page answered. Stores that failed any of those would be absent from every count rather than counted as zeros. In this run the rule never fired: all 20 answered all three requests, so every row's denominator is the full 20.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why isn't my Shopify redirect working?&lt;/strong&gt;&lt;br&gt;
The first thing to rule out is that the URL you are redirecting from is not actually broken. Shopify's rule is that redirects only fire on URLs that return an error: "If the URL still loads a valid webpage, then the URL redirect won't work." Open the old URL in a private window. If it loads a page, the redirect will never run, and no amount of editing it will change that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I redirect the uppercase version of a product URL to the lowercase one?&lt;/strong&gt;&lt;br&gt;
No. In our test all 20 stores returned 200 for the uppercase handle, which makes it a working URL and therefore not a redirect source. It is also not the problem it looks like: the page's canonical tag points at the lowercase URL on 19 of the 20 stores, which is the mechanism that resolves it for search engines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If I change a product handle, do I need a redirect?&lt;/strong&gt;&lt;br&gt;
That depends on one thing: whether the old URL is broken afterwards. We did not test handle changes, and Shopify's redirect documentation does not mention them, so check rather than take anyone's word — &lt;code&gt;curl -I&lt;/code&gt; the old URL after the change. A 404 means you can create the redirect. Anything that still serves a page means a redirect would never fire.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many redirects can I have?&lt;/strong&gt;&lt;br&gt;
Shopify documents 100,000 on standard plans and 20,000,000 on Plus. In practice the constraints that bite are the ineligible prefixes and fixed paths, not the ceiling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need a redirect for every deleted product?&lt;/strong&gt;&lt;br&gt;
No. A 404 is a correct answer for a product that is genuinely gone and had nothing pointing at it. Spend redirects on URLs that had inbound links, index presence, or traffic, and send those to the nearest real equivalent — usually the collection, not an unrelated product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will one redirect cover my &lt;code&gt;/en-ca&lt;/code&gt; storefront?&lt;/strong&gt;&lt;br&gt;
Shopify's documentation says both yes and no on the same page, so test it. Create the redirect at the root, then request the subfolder URL and see where you land. That takes a minute and beats either sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arvio: AI Store Operator — &lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-redirects&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-redirects_listing_end" rel="noopener noreferrer"&gt;install it on the Shopify App Store&lt;/a&gt;.&lt;/strong&gt; It reads your live catalogue and drafts the product fields that are missing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/shopify-redirects?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-redirects_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-redirects&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-redirects_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>GPTBot and your Shopify store: what your robots.txt actually says</title>
      <dc:creator>Arvio</dc:creator>
      <pubDate>Sat, 05 Sep 2026 01:27:11 +0000</pubDate>
      <link>https://dev.to/arvio/gptbot-and-your-shopify-store-what-your-robotstxt-actually-says-2mla</link>
      <guid>https://dev.to/arvio/gptbot-and-your-shopify-store-what-your-robotstxt-actually-says-2mla</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a repost. Originally published on the Arvio blog: &lt;a href="https://arvio.a.xyz/blog/shopify-gptbot?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-gptbot_origin_top" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-gptbot&lt;/a&gt;. The canonical URL points back there.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Published September 3, 2026 by &lt;a href="https://apps.shopify.com/partners/adot-inc?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-gptbot_listing1" rel="noopener noreferrer"&gt;Adot Technologies Inc&lt;/a&gt;, the team behind &lt;a href="https://apps.shopify.com/arvio?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-gptbot_listing2" rel="noopener noreferrer"&gt;Arvio: AI Store Operator&lt;/a&gt;. The counts below come from reading robots.txt on 58 live Shopify storefronts on September 3, 2026, plus a user-agent test run on our own domain.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Search "GPTBot" and page 1 is OpenAI explaining its own crawler, plus a row of bot-detection vendors explaining why you should buy bot detection. All of it is about GPTBot in general. None of it answers the question a Shopify merchant is actually holding: &lt;strong&gt;what is my store telling it right now, and did I choose that?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question has a checkable answer, and it is public. We read it on 58 live Shopify stores.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shopify ships every store a robots.txt you did not write.&lt;/strong&gt; It is 116 lines, and it names exactly two user agents: &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;adsbot-google&lt;/code&gt;. GPTBot is not one of them. 53 of the 58 stores we could read were serving a file that matches its shape exactly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One store in 58 names GPTBot at all, and that one allows it.&lt;/strong&gt; Zero of the 58 disallow GPTBot at the site root. Whatever the debate about AI crawling looks like online, it has not reached these stores' robots.txt files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A robots.txt asks; nothing enforces it.&lt;/strong&gt; We could only test that on a server we are allowed to test — ours, which blocks nobody. A request carrying a GPTBot user agent and one carrying a normal browser user agent came back identical across all 36 URLs in our sitemap: same status, same byte count, same visible character count. That is one server, and it shows a server that does not discriminate — not that no server does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Blocking GPTBot" is not "keeping my store out of ChatGPT."&lt;/strong&gt; OpenAI runs separate agents for training, for search, and for user-triggered fetches, and its documentation is explicit that the robots.txt settings are independent of each other. Disallowing the training crawler does not remove you from ChatGPT's search answers; disallowing the search crawler does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whichever way you want it, the fix is one template file&lt;/strong&gt; in your theme, and you can verify it from a browser in one request. The syntax is the easy part. What takes thought is which of the three agents you actually meant.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your robots.txt is a decision your store already made for you&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arvio audits what your storefront publishes about your products: catalogue fields, product metadata, page descriptions. It shows you what is there before you change anything — the robots.txt above is a theme file, and that one is yours to edit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-gptbot&amp;amp;source_section=blog_top&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-gptbot_listing_top" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  First, read your own file
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;https://yourstore.com/robots.txt&lt;/code&gt; in a browser tab. That is the whole diagnostic. It is a plain text file, it is public, and it is the file every well-behaved crawler reads before it reads anything else on your store.&lt;/p&gt;

&lt;p&gt;You will almost certainly see a long list of &lt;code&gt;Disallow:&lt;/code&gt; lines covering &lt;code&gt;/cart&lt;/code&gt;, &lt;code&gt;/checkout&lt;/code&gt;, &lt;code&gt;/orders&lt;/code&gt;, &lt;code&gt;/account&lt;/code&gt;, and a pile of &lt;code&gt;/collections/*sort_by*&lt;/code&gt; variants. That is Shopify's default, and it is a sensible default — it keeps crawlers out of the pages that are per-visitor or infinite.&lt;/p&gt;

&lt;p&gt;What you will not see, unless someone put it there, is any mention of GPTBot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Shopify ships you
&lt;/h2&gt;

&lt;p&gt;We fetched robots.txt from all 60 hostnames in a public sample of live Shopify storefronts. 58 answered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;53 of those 58 were serving a file with the platform default's exact shape.&lt;/strong&gt; We tested the shape, not the bytes — we did not diff the whole file, so "same shape" is not the same claim as "never touched": swapping a &lt;code&gt;Disallow&lt;/code&gt; path or adding a &lt;code&gt;Sitemap&lt;/code&gt; line changes neither number we counted. What we can say is that on both counts the file was indistinguishable from the default: 109 non-blank lines (116 including blank lines) and exactly two &lt;code&gt;User-agent&lt;/code&gt; groups — &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;adsbot-google&lt;/code&gt;. Every one of the 53 matched on both counts. The same default ships two other agent-facing things nobody installed either — &lt;a href="https://arvio.a.xyz/blog/shopify-agents-md-ucp?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-gptbot_blog4" rel="noopener noreferrer"&gt;an &lt;code&gt;agents.md&lt;/code&gt; instruction file and a live MCP endpoint&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The five that differed, by non-blank line count:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lines&lt;/th&gt;
&lt;th&gt;What it looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;193&lt;/td&gt;
&lt;td&gt;Seven groups: the default's &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;adsbot-google&lt;/code&gt;, plus five named ones — Ahrefs (two), MJ12bot, Nutch, Pinterest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;165&lt;/td&gt;
&lt;td&gt;Default plus eight named groups, including GPTBot. The only file in the sample that names it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;159&lt;/td&gt;
&lt;td&gt;A different store, the same seven groups as the 193-line file, fewer rules inside&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;159&lt;/td&gt;
&lt;td&gt;A third store, the same seven groups again — but every group header is malformed. See below&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Far shorter than the default; not the same kind of edit as the other four&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three days earlier, on August 31, we ran a narrower version of the same read against the same 60 hostnames: 59 answered, 54 were on the platform default, 5 were customised. The picture is stable. (That earlier file counted total lines including blanks, which is why its default is 116 and this one's is 109. Same file, two ways of counting — we re-fetched one default store to confirm both numbers describe it.)&lt;/p&gt;

&lt;h3&gt;
  
  
  The one store that names GPTBot allows it
&lt;/h3&gt;

&lt;p&gt;Of all 58, exactly one has a &lt;code&gt;User-agent: GPTBot&lt;/code&gt; group. That group carries no &lt;code&gt;Disallow&lt;/code&gt; line at all, which means: crawl anything.&lt;/p&gt;

&lt;p&gt;So the count that matters is the boring one. &lt;strong&gt;Stores in this sample that block GPTBot from the site root: 0 of 58.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That may not be what you expected. We measured these 58 storefronts and nothing else — we have no reading on what publishers outside this sample are doing, and this survey cannot give you one. Inside it, the file is untouched on this question.&lt;/p&gt;

&lt;h3&gt;
  
  
  One of the five customised files is broken
&lt;/h3&gt;

&lt;p&gt;One store's robots.txt has every group header collapsed onto one line with the rule that should follow it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: *&lt;span class="n"&gt;Disallow&lt;/span&gt;:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All seven of its groups look like that — the same seven the other two customised stores have — including the &lt;code&gt;*&lt;/code&gt; group that carries the cart and checkout rules. Our parser, which requires the header to be its own line, finds no valid &lt;code&gt;User-agent&lt;/code&gt; group in the file at all; we did not test it against Google's or OpenAI's parsers, so what any of them does with it is not something this survey measured. What the file does do is return 200 across 159 non-blank lines that look fine at a glance, which is the part worth knowing: nothing about it tells you it is broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A robots.txt does not report errors.&lt;/strong&gt; Nothing turns red. Nobody emails you. If you edit yours, the only way to know it worked is to read it back and check the shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  robots.txt is a request, not a lock
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;robots.txt&lt;/code&gt; is a file you publish saying what you would like crawlers to do. Well-behaved crawlers read it and comply. It is not enforced by your server, and it does not stop anything from arriving. If you want an actual block, that lives somewhere else — a rule at your CDN or firewall that refuses the request.&lt;/p&gt;

&lt;p&gt;We wanted to know whether a server treats a GPTBot request differently from a browser request, and our own domain is the only place we can ask that. Our sitemap has 36 URLs. We requested each one twice: once with OpenAI's published GPTBot user-agent string, once with an ordinary desktop Chrome user-agent string. Then we compared status code, response size in bytes, and the visible character count after stripping tags and scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All 36 came back identical on all three measures.&lt;/strong&gt; Same 200, same byte count, same visible text. The server does not know or care which one is asking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We did not send a GPTBot user agent to anyone else's store.&lt;/strong&gt; Impersonating another company's crawler against servers that may be trying to filter it is a different act from testing your own property, and we did not do it. Every third-party number in this article comes from reading a public robots.txt with our own clearly-identified research agent. So this article can tell you what 58 stores say, and what one server (ours) does. It cannot tell you what those 58 servers do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three agents are not one switch
&lt;/h2&gt;

&lt;p&gt;This is where most "how to block AI crawlers" advice quietly gives you the wrong outcome.&lt;/p&gt;

&lt;p&gt;OpenAI does not run one crawler. Reading its bot documentation on September 3, 2026, the relevant ones are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;What OpenAI says it is for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPTBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Crawling content that may be used in training its generative AI foundation models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OAI-SearchBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Surfacing websites in search results in ChatGPT's search features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ChatGPT-User&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Visiting a page because a user in ChatGPT asked something that required it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And the documentation is explicit that these are independent controls: "Each setting is independent of the others – for example, a webmaster can allow OAI-SearchBot in order to appear in search results while disallowing GPTBot to indicate that crawled content should not be used for training OpenAI's generative AI foundation models."&lt;/p&gt;

&lt;p&gt;It also states the consequence of the one people rarely mean to pick: "Sites that are opted out of OAI-SearchBot will not be shown in ChatGPT search answers, though can still appear as navigational links."&lt;/p&gt;

&lt;p&gt;So the two mistakes are symmetric, and both are common:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You block &lt;code&gt;GPTBot&lt;/code&gt; because you do not want your product copy in a training set. Fine — that is exactly what it does, and it does &lt;strong&gt;not&lt;/strong&gt; take you out of ChatGPT search.&lt;/li&gt;
&lt;li&gt;You block everything with &lt;code&gt;openai&lt;/code&gt; in the name because you want "AI out of my store." That includes &lt;code&gt;OAI-SearchBot&lt;/code&gt;, and it does take you out of ChatGPT search results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most stores selling things, those two sentences point in opposite directions, and the decision is genuinely yours to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to change it on Shopify
&lt;/h2&gt;

&lt;p&gt;Shopify lets you edit the file through a theme template. The steps, from the admin:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Online Store → Themes → ⋯ → Edit code&lt;/strong&gt; on your live theme.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Templates&lt;/strong&gt;, click &lt;strong&gt;Add a new template&lt;/strong&gt;, choose &lt;strong&gt;robots.txt&lt;/strong&gt; from the template-type list, and create it. Shopify creates &lt;code&gt;robots.txt.liquid&lt;/code&gt; prefilled with the default rules — it renders the same file you are serving today.&lt;/li&gt;
&lt;li&gt;Add your own &lt;code&gt;User-agent&lt;/code&gt; groups &lt;strong&gt;above or below&lt;/strong&gt; the default block. Do not delete the default block; those &lt;code&gt;Disallow&lt;/code&gt; rules are keeping crawlers out of carts and checkouts.&lt;/li&gt;
&lt;li&gt;Save.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read it back&lt;/strong&gt;: open &lt;code&gt;https://yourstore.com/robots.txt&lt;/code&gt; in a browser and confirm your group is there, on its own line, spelled the way you meant.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A group that disallows the training crawler while leaving search alone looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;GPTBot&lt;/span&gt;
&lt;span class="n"&gt;Disallow&lt;/span&gt;: /

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;OAI&lt;/span&gt;-&lt;span class="n"&gt;SearchBot&lt;/span&gt;
&lt;span class="n"&gt;Allow&lt;/span&gt;: /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The opposite decision, letting everything in, is simply the absence of any &lt;code&gt;Disallow&lt;/code&gt; for those agents. That is what all 58 stores we read have today: 57 because the file never mentions these agents, and one because it names &lt;code&gt;GPTBot&lt;/code&gt; and allows it.&lt;/p&gt;

&lt;p&gt;Three things that bite people at step 5:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent names are exact tokens.&lt;/strong&gt; &lt;code&gt;GPTBot&lt;/code&gt; is the agent name. &lt;code&gt;GPT-Bot&lt;/code&gt;, &lt;code&gt;OpenAI&lt;/code&gt; and &lt;code&gt;ChatGPT&lt;/code&gt; are not that agent, and a group headed by one of them silently matches nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Disallow:&lt;/code&gt; with nothing after it means "allow everything."&lt;/strong&gt; &lt;code&gt;Disallow: /&lt;/code&gt; is the block. The empty one is the opposite of what it looks like.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The group header has to be its own line.&lt;/strong&gt; That malformed store above is what happens when it is not: 200 back, 159 non-blank lines, nothing that looks wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You changed the file. Did the rest of the store follow?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A crawler that is allowed in still only reads what your storefront publishes. Arvio audits that surface: catalogue fields, product metadata, page descriptions. It drafts the changes and you approve them before anything goes live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-gptbot&amp;amp;source_section=blog_mid&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-gptbot_listing_mid" rel="noopener noreferrer"&gt;Install Arvio on the Shopify App Store →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  So should you block it?
&lt;/h2&gt;

&lt;p&gt;We make a Shopify app, and an app that helps you fill in catalogue fields does better in a world where assistants read catalogues. What the measurement supports is narrower:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Right now these agents get whatever you give every other crawler.&lt;/strong&gt; The default file has no opinion about them, so they fall through to the &lt;code&gt;*&lt;/code&gt; group, and 53 of 58 stores look untouched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If your reason is "I don't want my writing used for training," &lt;code&gt;GPTBot&lt;/code&gt; is the agent that maps to that&lt;/strong&gt;, and blocking it costs you nothing in ChatGPT's search surface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If your reason is "I want to be findable when someone asks an assistant for what I sell," blocking is the wrong move.&lt;/strong&gt; Being allowed in is the floor, not the finish — &lt;a href="https://arvio.a.xyz/blog/chatgpt-shopping-shopify-catalog?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-gptbot_blog6" rel="noopener noreferrer"&gt;what an assistant actually reads out of a Shopify catalogue&lt;/a&gt; is a separate question from whether it is allowed to read it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you want a block that holds against traffic that ignores robots.txt,&lt;/strong&gt; this file will not do it. That is a CDN or firewall rule, and a different piece of work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this article does not know
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;We have no server logs for these stores&lt;/strong&gt;, so nothing here says whether GPTBot has ever visited any of them, how often, or what it took.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;58 stores is a sample&lt;/strong&gt;, drawn from a public list of stores whose owners posted their own URL asking for feedback. We did not measure their age, their revenue or their catalogue size, so we cannot tell you how that frame skews — only what it is: stores whose owners posted their own URL asking strangers for feedback. It is not the Shopify population, and certainly not the large-merchant population, where a CDN in front of the store makes all of this look different.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;robots.txt changes the day someone edits it.&lt;/strong&gt; Our two reads are three days apart and agree. That is stability over three days, not a law.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The sample.&lt;/strong&gt; 60 hostnames drawn from a frame of 230 live Shopify storefronts, built on August 26, 2026 from 474 publicly posted store URLs — 244 of those no longer served a public catalogue and were dropped, so the frame is what survived. The draw is &lt;code&gt;random.Random(20260826).sample(frame, 60)&lt;/code&gt;; 58 of the 60 answered. We have not published the frame file or the per-store readings, because they name 58 real storefronts — but nothing here depends on our copy: &lt;code&gt;robots.txt&lt;/code&gt; is public on every Shopify store, and one &lt;code&gt;curl -s https://anystore.com/robots.txt&lt;/code&gt; reproduces the unit of measurement we used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The robots read (September 3, 2026).&lt;/strong&gt; One &lt;code&gt;GET /robots.txt&lt;/code&gt; per host, at one request per second, with a user agent that identifies itself as our research crawler and carries a contact address. 58 of 60 returned a readable file; 2 did not answer. For each, we parsed the file into &lt;code&gt;User-agent&lt;/code&gt; groups and recorded: non-blank line count, the agent names present, whether any group names &lt;code&gt;GPTBot&lt;/code&gt;, and the &lt;code&gt;Disallow&lt;/code&gt; rules inside that group if it exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Platform default" is a shape test, not a diff.&lt;/strong&gt; A file counted as default if it had 109 non-blank lines and exactly the two agent groups &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;adsbot-google&lt;/code&gt;. 53 of 58 matched. We separately fetched one of those files whole to confirm the line counts reconcile with the August 31 read (116 lines including blanks).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The user-agent test (September 3, 2026, our domain only).&lt;/strong&gt; Every &lt;code&gt;&amp;lt;loc&amp;gt;&lt;/code&gt; in our own sitemap — 36 URLs — requested twice, once with OpenAI's published GPTBot user-agent string and once with a desktop Chrome user-agent string, one second apart. Recorded: HTTP status, response bytes, and visible characters after removing &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; and all tags. Three of the 36 needed a re-request on the first pass. Final: 36/36 identical on all three measures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The OpenAI documentation&lt;/strong&gt; (&lt;a href="https://platform.openai.com/docs/bots" rel="noopener noreferrer"&gt;platform.openai.com/docs/bots&lt;/a&gt;) was fetched the same day (HTTP 200) and the quoted sentences are copied verbatim from that response. That page is public, so every quotation in this article is one you can check yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusion rule.&lt;/strong&gt; Two hosts that did not return a readable robots.txt are excluded from every denominator in this article. They are counted in "60 asked", never in "58 read."&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does Shopify block GPTBot by default?&lt;/strong&gt;&lt;br&gt;
No. The default robots.txt Shopify serves does not mention GPTBot in any way. It names two user agents — &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;adsbot-google&lt;/code&gt; — and the rules under &lt;code&gt;*&lt;/code&gt; are about carts, checkouts, accounts and sorted collection URLs. In our sample, 53 of 58 were serving a file with that exact shape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If I block GPTBot, do I disappear from ChatGPT?&lt;/strong&gt;&lt;br&gt;
Not from its search answers. Per OpenAI's own documentation, &lt;code&gt;GPTBot&lt;/code&gt; governs crawling for model training and &lt;code&gt;OAI-SearchBot&lt;/code&gt; governs appearing in ChatGPT's search results, and the two settings are independent. If disappearing from ChatGPT search is what you want, &lt;code&gt;OAI-SearchBot&lt;/code&gt; is the agent you would disallow — and OpenAI states that opted-out sites "will not be shown in ChatGPT search answers, though can still appear as navigational links."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will blocking it actually stop anything?&lt;/strong&gt;&lt;br&gt;
It stops crawlers that read robots.txt and comply, which is what the named, documented ones do. Nothing enforces it. Our own-domain test found the server returning identical responses to a GPTBot user agent and a browser user agent across all 36 sitemap URLs — nothing at the server layer was distinguishing them. If you need enforcement, that is a CDN or firewall rule, not this file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I check whether my edit worked?&lt;/strong&gt;&lt;br&gt;
Open &lt;code&gt;https://yourstore.com/robots.txt&lt;/code&gt; in a browser and read it. Confirm your &lt;code&gt;User-agent:&lt;/code&gt; line is on a line by itself, that the agent name is spelled exactly, and that &lt;code&gt;Disallow: /&lt;/code&gt; is present if a block is what you meant — an empty &lt;code&gt;Disallow:&lt;/code&gt; allows everything. One store in our sample of 58 has a malformed group header and still serves a 200 that looks entirely normal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does any of this affect Google?&lt;/strong&gt;&lt;br&gt;
Not directly. Google's crawlers are separate agents with separate rules, and the Shopify default names only &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;adsbot-google&lt;/code&gt;, no Google AI agent of any kind. Nothing in this article changes how &lt;code&gt;Googlebot&lt;/code&gt; sees your store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arvio: AI Store Operator — &lt;a href="https://apps.shopify.com/arvio?article_slug=shopify-gptbot&amp;amp;source_section=blog_end&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-gptbot_listing_end" rel="noopener noreferrer"&gt;install it on the Shopify App Store&lt;/a&gt;.&lt;/strong&gt; Once a crawler is allowed in, what it reads is your catalogue fields — and that is the part Arvio works on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://arvio.a.xyz/blog/shopify-gptbot?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-gptbot_origin_foot" rel="noopener noreferrer"&gt;https://arvio.a.xyz/blog/shopify-gptbot&lt;/a&gt;. More Shopify bulk-editing writeups are on &lt;a href="https://arvio.a.xyz/blog/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=devto_blog&amp;amp;utm_content=shopify-gptbot_bloghome" rel="noopener noreferrer"&gt;the Arvio blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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