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.
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.
| App | Subscription price in initial HTML? | JSON-LD output | AI 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:
| unitCode | Meaning | Use case |
|---|---|---|
MON | Month | Monthly subscription (coffee, supplements, pet food) |
WEE | Week | Weekly delivery (meal kits, fresh produce) |
ANN | Year | Annual subscription (software, services) |
DAY | Day | Daily delivery (newspaper, medication) |
Shopify subscription product errors AI agents encounter
| Error | AI agent impact | Fix |
|---|---|---|
| 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