/ โ† AI Schema Generator Fixes / AI Schema on Shopify
In this guide
  1. What a Shopify product page already outputs
  2. Why a pasted block goes stale on Shopify
  3. Deciding who owns each property
  4. Turning generated output into Liquid
  5. Per-product extras, and moving them in bulk
  6. Review apps and ratings
  7. The fix, step by step
  8. Variants, markets and theme updates
  9. Questions and answers

What a Shopify product page already outputs

Before adding anything, find out what is there. Open a product page, view the source and search for application/ld+json. On a typical store you will find markup from up to three places:

Run the product URL through the Rich Results Test and open the Product snippets and Merchant listings items. If one Product appears with no errors and the fields you care about, you may not need generated markup on this template at all. If there are two or three Product items, sort that out first with the duplicates guide, because adding a generated block would make a fourth.

๐Ÿ’ก Note the theme name and version, and every app that writes markup, in a short document. When something changes after an update, you will know what the page looked like before.

Why a pasted block goes stale on Shopify

The generator reads the product page as it is when you run it. Everything it writes is fixed from then on unless you change it. On a Shopify store, many of those values move without anyone touching the markup:

PropertyChanges whenResult if pasted
offers.priceA sale starts, a price is edited, a discount endsMarkup price disagrees with the page and the product feed
offers.availabilityStock sells out or is restockedInStock shown for a sold-out item, or the reverse
offers.priceCurrencyA visitor from another market sees a converted priceGBP markup beside a euro price
sku, gtinThe shopper picks another variantIdentifiers for the wrong size or colour
imagePhotos are reordered or replacedAddresses that no longer exist on the CDN
aggregateRatingA new review arrivesA frozen score and count that drift from the widget

By contrast, some things the generator does well are stable: the material, the dimensions, care instructions, the intended audience, a clean description, and the choice between Product and ProductGroup. Those are the parts worth keeping as written.

โš ๏ธ A price in markup that disagrees with the page is not a cosmetic problem. Google compares them, and if you also send a feed to Merchant Center, mismatches there can lead to products being disapproved.

Deciding who owns each property

The simplest reliable arrangement is one Product block per page, built by your theme, with generated content used to improve it. Split the properties like this:

PropertyOwnerLiquid or source
nameShopifyproduct.title
url, @idShopifycanonical_url
imageShopifyproduct.images with image_url
price, priceCurrency, availabilityShopifyThe selected variant and cart.currency.iso_code
sku, gtinShopifyvariant.sku, variant.barcode
brandShopify or metafieldproduct.vendor if it holds the brand, otherwise a metafield
descriptionGenerated, reviewedA metafield, or the product description stripped of HTML
material, size, audience and similar extrasGenerated, reviewedProduct metafields
aggregateRating, reviewReview appThe app's own output, and nothing else
Shipping and returnsStore-wideOne shared snippet, or Merchant Center account settings

Everything in the Shopify rows is read at page load, so it can never go stale. Everything in the generated rows is stable information that a person checks once per product.

Turning generated output into Liquid

Take one good generated block and replace each moving value with the Liquid that produces it. The json filter wraps text in quotes and escapes anything that would break the block, such as inch marks in a product title. The image_url filter returns an address starting with //, so https: is added in front.

{%- assign v = product.selected_or_first_available_variant -%}
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "@id": "{{ canonical_url }}#product",
  "name": {{ product.title | json }},
  "url": "{{ canonical_url }}",
  "description": {{ product.metafields.custom.schema_description.value | default: product.description | strip_html | strip_newlines | json }},
  "image": [
    {%- for img in product.images limit: 3 -%}
      "https:{{ img | image_url: width: 1200 }}"{% unless forloop.last %},{% endunless %}
    {%- endfor -%}
  ],
  "sku": {{ v.sku | json }},
  {%- if v.barcode != blank %}
  "gtin": {{ v.barcode | json }},
  {%- endif %}
  {%- if product.metafields.custom.material != blank %}
  "material": {{ product.metafields.custom.material.value | json }},
  {%- endif %}
  "brand": { "@type": "Brand", "name": {{ product.vendor | json }} },
  "offers": {
    "@type": "Offer",
    "url": "{{ canonical_url }}",
    "price": "{{ v.price | divided_by: 100.0 }}",
    "priceCurrency": "{{ cart.currency.iso_code }}",
    "availability": "https://schema.org/{% if v.available %}InStock{% else %}OutOfStock{% endif %}",
    "itemCondition": "https://schema.org/NewCondition"
  }
}
</script>

A few details that matter:

Per-product extras, and moving them in bulk

The parts of generated markup worth keeping, such as a tidy description, material, dimensions or care details, belong in product metafields. Create the definitions once in Settings, Custom data, Products, for example custom.material as single-line text and custom.schema_description as multi-line text. They then appear as fields on every product in the admin, and the Liquid above reads them.

Working through a catalogue

  1. Generate markup for a sample of products from each product type and review it against the product pages.
  2. Copy the stable values you want to keep into a spreadsheet, one row per product handle, one column per metafield.
  3. Check each row against the product. Delete anything that is not stated on the page or in your supplier data.
  4. Load the columns into the metafields. Shopify's bulk editor can show metafield columns for editing, and the product CSV import in current versions can include metafield columns; for large catalogues a bulk import app is another option.
  5. Spot-check a few products in the Rich Results Test after the import.
๐Ÿ’ก Export before you import. A product CSV export taken first gives you a way back if a column lands in the wrong field.

Review apps and ratings

Generated markup is at its most dangerous around ratings. The model may read a star widget, a "4.8 from 212 reviews" line or a single testimonial and write an aggregateRating. On Shopify, review widgets usually load with JavaScript after the page, so the generator may have seen stale numbers, placeholder text or nothing at all.

The fix, step by step

Step 1

See what the theme and apps already output

Open a product page, view the source and search for application/ld+json. Note every block, which file or app produced it and which types it contains. Run the same URL through the Rich Results Test and open the Product snippets and Merchant listings items. If there is already more than one Product, choose which one will survive before going further, because a generated block added on top would only make the conflict worse.

Step 2

Generate from one representative product

Pick a product with several variants, a full description, a barcode and some reviews, so the generator sees the widest set of fields. Use its live, public address on your primary domain, not the myshopify.com address or a theme preview link. Review the output against the page and delete any value you cannot see there, especially ratings, prices and identifiers that the model may have guessed or copied from the wrong variant.

Step 3

Decide which source owns each property

Go through the reviewed block line by line and label each property: Shopify data, a metafield, the review app or store-wide settings. Prices, stock, currency, images, identifiers and addresses should always be Shopify data read at page load. Ratings belong to the review app. The stable descriptive extras are the generator's real contribution. Anything with no owner and no true source gets removed rather than kept as fixed text.

Step 4

Turn the moving values into Liquid

Rewrite the block as a Liquid snippet, replacing each Shopify-owned value with the object that produces it. Output text with the json filter so quotes and inch marks cannot break the block, divide variant.price by 100.0 rather than using money filters, take the currency from cart.currency.iso_code, and prefix image_url output with https:. Wrap optional fields in conditions so products without them stay valid.

Step 5

Move per-product extras into metafields

Create product metafield definitions for each stable extra you decided to keep, such as material or a plain-text schema description. Fill them for your sample products by hand, then for the rest of the catalogue in bulk through the bulk editor, a CSV import or an import app, after exporting a backup. Read each metafield in the snippet, wrapped in a condition, so that products with an empty field simply leave the property out of the block.

Step 6

Add the snippet to the product template

Duplicate the live theme first so you have a copy to return to. In the code editor, save the Liquid as a snippet such as snippets/product-schema.liquid and render it from the main product section with the product passed in. Then remove or disable the theme's existing Product output, usually a structured_data line or an older snippet, so that the page carries exactly one Product block.

Step 7

Test several products, variants and markets

Preview the duplicated theme and test a simple product, a product with variants, a sold-out product and one with no barcode in the Rich Results Test. Select a different variant and confirm the price, SKU and availability change with it. If you sell in more than one market, check a page in another currency. Publish the theme, request indexing for a few products and watch both Search Console product reports.

Variants, markets and theme updates

Products with variants

The snippet above describes the selected variant, which is what the page shows when opened with a ?variant= parameter and the first available variant otherwise. That is a sound starting point. If you want every variant described, the pattern is a ProductGroup with one Product per variant in hasVariant, which means looping over product.variants in the snippet. Generated markup for a variant product is useful here as a model of the structure, but it will have listed only the variants the model saw, with the prices it saw.

Several markets and domains

If each market has its own domain or subfolder, canonical_url follows the page being viewed, so the markup's addresses stay correct. Keep the currency code dynamic; a generated block with a fixed GBP will be wrong on every non-UK page.

Theme updates

Updating a theme from the theme store creates a new version, and edits you made directly in the theme code may not come across. After any theme update, check that your snippet is still rendered and that the theme's own Product output has not been switched back on. Metafield values are stored against products, not the theme, so they survive.

Questions and answers

1. Can I just paste generated schema into a Shopify product description?

You can, but it is the least reliable place for it. The rich text editor can strip or alter script tags, the markup cannot change when the price or stock does, and it is duplicated separately on every product. It also sits alongside whatever Product markup your theme already outputs, giving Google two conflicting descriptions. Using the generated block as a pattern for a Liquid snippet, with stable extras in metafields, avoids all of those problems.

2. Does Shopify already add Product schema?

Most current themes do. Dawn and many other Online Store 2.0 themes use the structured_data Liquid filter to output Product markup on product pages, and older themes often include a hand-written block in a snippet. What they output varies by theme and version, so view the source of a product page and run it through the Rich Results Test to see exactly which fields are already there before deciding what generated markup should add.

3. Why is my Shopify schema price wrong?

The usual causes are a pasted block that has not been updated since a price change, a money filter that added a currency sign or a thousands comma, or a fixed currency code shown next to a converted price for visitors from another market. Read the price from the selected variant and divide it by 100.0, take the currency code from the cart, and remove any fixed prices from generated markup stored in the page or a metafield.

4. Should my review app or my own snippet output the rating?

One or the other, never both. The review app knows the live score and count, so it is the natural owner. Most review apps output rating markup themselves, and several can write Shopify's standard review metafields that a theme snippet can read instead. Whichever route you choose, remove any rating from generated markup, because a figure the model read from the page on one day will drift away from the real score as new reviews arrive.

5. Will generated schema help my products appear in Google Shopping?

Structured data on product pages can make them eligible for merchant listings, which include shopping-style results in Search, without a Merchant Center account. It does not guarantee they appear, and the Shopping tab also draws on product feeds. If you use Shopify's Google channel to send a feed, the price and availability in your markup must match the feed and the page, so keep both driven by live Shopify data.

6. How do I handle schema for products with many variants?

For most stores, describing the variant currently selected on the page is enough, and it keeps price, SKU and availability accurate as shoppers change options. If you want Google to understand the full range, use a ProductGroup with one Product per variant, generated by looping over the product's variants in Liquid. Treat any generated variant markup as a structural example only, since it will list just the variants and prices the model happened to see.

7. Do theme updates remove custom schema snippets?

They can. When you update a theme from the theme store, Shopify adds the new version to your theme library, and changes made directly in theme code may need to be applied again. Keep your snippet in its own file so it is easy to copy across, note the line you added to render it, and check that the theme's built-in Product output is still switched off. Metafield data lives with the products, so it is not affected.

Generate a Product pattern for your theme

The AI Schema Generator reads your page content with an AI model, writes JSON-LD tailored to the content type and validates it against schema.org and Google rich result criteria before delivery, ready for you to review and adapt to your store.

Open AI Schema Generator Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All AI Schema Generator Fixes  ·  Duplicate Schema Generation  ·  Product Schema Errors  ·  JSON-LD on Shopify  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.