CatalogScan

SEO Guide · 2026

Shopify Subscription Products and AI Shopping Agents: Markup, Visibility, and the Subscribe-and-Save Gap

Subscription products are invisible to most AI shopping agents — not because AI can't index them, but because Recharge, Bold, and Stay.ai inject subscription pricing via JavaScript that crawlers never see. Here's how to fix it with PriceSpecification JSON-LD.

TL;DR Popular Shopify subscription apps (Recharge, Bold, Stay.ai) inject subscription pricing via JavaScript — AI crawlers only see the one-time price from your initial HTML. Fix: add a second Offer block to your product JSON-LD with PriceSpecification for the subscription price (priceType: SalePrice, unitText: "per delivery"). Google AI Mode and ChatGPT Shopping will then display "Subscribe & save" callouts and the subscription discount percentage.

The subscription pricing gap: what AI agents see vs. what's on your page

Most Shopify subscription apps work by intercepting the add-to-cart flow and dynamically displaying subscription pricing when a customer selects the subscription option. From the shopper's browser, this looks seamless — the price updates in real-time. From an AI crawler's perspective, the initial HTML response contains only the standard Shopify product price (the one-time price), and the subscription price exists only in JavaScript state that executes after the page loads.

AppSubscription price in initial HTML?JSON-LD outputAI agent visibility
Recharge No — JavaScript injection No subscription JSON-LD added AI sees only one-time price
Bold Subscriptions No — JavaScript injection No subscription JSON-LD added AI sees only one-time price
Stay.ai No — JavaScript injection No subscription JSON-LD added AI sees only one-time price
Skio No — JavaScript injection No subscription JSON-LD added AI sees only one-time price
Manual Liquid JSON-LD Yes — server-side render Full PriceSpecification for both offer types AI sees both prices; displays subscription callout

PriceSpecification JSON-LD for subscribe-and-save products

The correct approach is to define two Offer blocks in your product JSON-LD — one for the one-time purchase, one for the subscription — both rendered in the initial HTML by Liquid:


{% assign sub_price = product.selected_or_first_available_variant.price | times: 0.85 | money_without_currency %}
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "{{ product.title | escape }}",
  "offers": [
    {
      "@type": "Offer",
      "name": "One-time purchase",
      "price": "{{ product.selected_or_first_available_variant.price | money_without_currency }}",
      "priceCurrency": "{{ shop.currency }}",
      "availability": "{% if product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}",
      "priceSpecification": {
        "@type": "UnitPriceSpecification",
        "priceType": "https://schema.org/ListPrice",
        "price": "{{ product.selected_or_first_available_variant.price | money_without_currency }}",
        "priceCurrency": "{{ shop.currency }}"
      }
    },
    {
      "@type": "Offer",
      "name": "Subscribe & save 15%",
      "price": "{{ sub_price }}",
      "priceCurrency": "{{ shop.currency }}",
      "availability": "{% if product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}",
      "priceSpecification": {
        "@type": "UnitPriceSpecification",
        "priceType": "https://schema.org/SalePrice",
        "price": "{{ sub_price }}",
        "priceCurrency": "{{ shop.currency }}",
        "unitText": "per delivery",
        "referenceQuantity": {
          "@type": "QuantitativeValue",
          "value": 1,
          "unitCode": "MON"
        }
      }
    }
  ]
}

Replace the 0.85 multiplier with your actual subscription discount percentage. In practice, store this as a product metafield (subscription.discount_pct) and use {{ product.metafields.subscription.discount_pct }} in the Liquid template to make it configurable per-product.

Using metafields to store subscription discount for Liquid output

If your subscription discount varies by product, store it in a product metafield:

# Set subscription discount metafield via Shopify Admin API
curl -X POST \
  "https://YOUR_SHOP.myshopify.com/admin/api/2024-01/products/PRODUCT_ID/metafields.json" \
  -H "X-Shopify-Access-Token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "metafield": {
      "namespace": "subscription",
      "key": "discount_pct",
      "value": "15",
      "type": "number_integer"
    }
  }'

# In Liquid: {{ product.metafields.subscription.discount_pct }}

What "unitCode MON" means for AI agents

The unitCode field uses UN/CEFACT unit codes. MON = month. AI shopping agents use this to display pricing in the context of the billing period. Other relevant unit codes for subscription products:

unitCodeMeaningUse case
MONMonthMonthly subscription (coffee, supplements, pet food)
WEEWeekWeekly delivery (meal kits, fresh produce)
ANNYearAnnual subscription (software, services)
DAYDayDaily delivery (newspaper, medication)

Shopify subscription product errors AI agents encounter

ErrorAI agent impactFix
Only one-time price in JSON-LD Subscription offer invisible; no "save X%" callout Add second Offer block for subscription with PriceSpecification
Subscription price injected via JS AI crawlers see only initial HTML; one-time price only Render subscription offer in Liquid server-side
Missing unitText AI shows subscription price as one-time price; misleads shoppers Add "unitText": "per delivery" or "per month"
Subscription price below zero Schema validation error; AI agents may ignore the Offer block Verify discount calculation: price × (1 - discount_pct/100)
priceType uses wrong enumeration Validation warning; subscription savings may not display Use "priceType": "https://schema.org/SalePrice"

Frequently asked questions

Do AI shopping agents recommend subscription products from Shopify?

Yes, but most subscription products are effectively invisible because Recharge, Bold, and Stay.ai inject subscription pricing via JavaScript — AI crawlers only see the one-time price from the initial HTML. ChatGPT Shopping and Google AI Mode can surface subscription products in recommendations when pricing is clearly marked up with PriceSpecification JSON-LD, but without it, the product appears as a standard one-time purchase.

Does Recharge or Bold Subscriptions add JSON-LD structured data automatically?

Neither app adds JSON-LD structured data for subscription pricing automatically as of 2026. Both rely on client-side JavaScript price injection, which is invisible to AI crawlers. To fix this, add custom Liquid JSON-LD that includes both pricing options as separate Offer blocks with PriceSpecification rendered server-side in the initial HTML.

What is the correct schema markup for subscribe-and-save pricing?

Use two separate Offer blocks within a Product.offers array. The subscription offer uses PriceSpecification with priceType set to SalePrice and unitText set to "per delivery" or "per month". The one-time offer uses a standard Offer block with the one-time price. This dual-offer approach lets AI agents distinguish between purchase options and surface the subscription savings.

How do subscription products appear in Google Shopping AI Mode?

Google Shopping AI Mode displays subscription products with a "Subscribe & save" label and the savings percentage when the product's structured data includes two Offer blocks with clearly differentiated prices — specifically when one Offer has priceType: SalePrice and a lower price than the other. Without this, Google sees only one price and cannot generate the subscription-specific display — a significant missed opportunity since subscription products with clear markup receive prominent callouts that one-time products don't.

Check your subscription product structured data

CatalogScan identifies missing subscription PriceSpecification markup and 17 other AI-agent-critical signals in 2 minutes.

Scan your store free