Shopify Structured Data
Loyalty program structured data
for Shopify and AI shopping agents
Why loyalty app widgets are invisible to AI crawlers — and how to surface points, member pricing, and rewards benefits through Shopify Product schema.
additionalProperty on your Offer, plus a ProgramMembership entity on your Organization. AI agents then read your loyalty benefits as structured text.
The loyalty app visibility gap
Shopify loyalty apps are enormously popular — Smile.io alone powers loyalty programs for over 100,000 merchants. But all of them inject their UI (points balance, rewards notifications, member prices) via JavaScript after the initial page load.
AI shopping agent crawlers — GPTBot, PerplexityBot, Google-Extended — do not execute JavaScript. When they crawl your product page, the loyalty widget is absent. The structured data output from your Shopify theme contains no loyalty signals at all. From an AI agent's perspective, your store has no loyalty program.
This is a significant missed opportunity as AI agents increasingly answer queries like "which stores give you points on every purchase?" or "best loyalty programs for outdoor gear."
Loyalty app structured data audit: every major Shopify app
| App | Renders via | Adds JSON-LD? | AI-agent readable? |
|---|---|---|---|
| Smile.io | Client-side JS widget | No | No |
| Yotpo Loyalty & Referrals | Client-side JS widget | No | No |
| LoyaltyLion | Client-side JS widget | No | No |
| Stamped Loyalty | Client-side JS widget | No | No |
| Rivo | Client-side JS widget | No | No |
| Growave | Client-side JS widget | No | No |
| Custom Liquid implementation | Server-side Liquid | Yes (if coded) | Yes |
Schema.org patterns for loyalty programs
1. Organization-level: ProgramMembership
Declare your loyalty program at the Organization level. This tells AI agents that your brand runs a named program — useful for queries like "stores with a VIP loyalty program."
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Your Brand",
"url": "https://yourstore.com",
"memberOf": {
"@type": "ProgramMembership",
"name": "YourBrand Rewards",
"programName": "YourBrand Rewards",
"url": "https://yourstore.com/pages/rewards",
"description": "Earn 1 point per $1 spent. Redeem 100 points for $1 off. Free to join.",
"membershipNumber": "Free membership"
}
}
</script>
2. Product-level: additionalProperty for point earn rate
Use Offer.additionalProperty to signal how many points a shopper earns on this product. Store the earn rate in a Shopify metafield and pull it into Liquid:
<!-- Assumes metafield: product.metafields.loyalty.points_earn_rate -->
{
"@type": "Offer",
"price": "{{ product.price | money_without_currency | remove: ',' }}",
"priceCurrency": "{{ cart.currency.iso_code }}",
"availability": "{% if product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}",
"additionalProperty": [
{
"@type": "PropertyValue",
"name": "Loyalty Points Earned",
"value": "{{ product.metafields.loyalty.points_earn_rate | default: product.price | divided_by: 100 | floor }} points",
"description": "Points earned on this purchase through YourBrand Rewards"
},
{
"@type": "PropertyValue",
"name": "Loyalty Program",
"value": "YourBrand Rewards"
}
]
}
3. Member pricing as a second Offer
If you offer a member price (e.g., 10% off for loyalty members), output it as a second Offer entity with member eligibility noted:
{
"@type": "Product",
"name": "{{ product.title | escape }}",
"offers": [
{
"@type": "Offer",
"name": "Regular price",
"price": "{{ product.price | money_without_currency | remove: ',' }}",
"priceCurrency": "{{ cart.currency.iso_code }}",
"availability": "{% if product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}"
}
{% if product.metafields.loyalty.member_price != blank %}
,{
"@type": "Offer",
"name": "Member price",
"description": "VIP member price — free to join YourBrand Rewards",
"price": "{{ product.metafields.loyalty.member_price | money_without_currency | remove: ',' }}",
"priceCurrency": "{{ cart.currency.iso_code }}",
"eligibleCustomerType": "https://schema.org/BusinessCustomer",
"availability": "{% if product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}"
}
{% endif %}
]
}
AI agent behavior: loyalty queries
| Query type | No loyalty schema | With loyalty schema |
|---|---|---|
| "Best rewards for [product category]" | Store not surfaced in loyalty-aware comparisons | Brand may appear with points benefit cited |
| "[Brand] loyalty program review" | AI must rely on external blog posts | ProgramMembership data used directly in answer |
| "How many points do I earn at [brand]?" | No structured answer available | AI reads additionalProperty earn rate |
| "[Brand] member discount" | AI has no structured price data | Second Offer with member price is readable |
| "Stores with free shipping for members" | Not surfaced | MerchantReturnPolicy + member benefit in schema |
Storing loyalty data in Shopify metafields
The bridge between your loyalty app and server-side Liquid output is Shopify metafields. Most loyalty apps expose data via their API that you can sync to product or shop metafields using a webhook or scheduled job:
- Create metafield definition:
loyalty.points_earn_rate(type: number_integer) at the product level - Use the app's webhook (order created/fulfilled) to calculate points and write to metafield via Shopify Admin API
- In your product Liquid template, read
product.metafields.loyalty.points_earn_rateand inject into JSON-LD
This pattern works for any loyalty app that exposes an API — which all major apps do. The webhook-to-metafield sync is a one-time implementation that keeps data fresh without manual updates.
Free shipping for members: MerchantReturnPolicy and ShippingDetails
If your loyalty program includes free shipping, add a shippingDetails note on the Offer that references member eligibility:
{
"@type": "Offer",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "0",
"currency": "USD"
},
"shippingDestination": {
"@type": "DefinedRegion",
"addressCountry": "US"
},
"eligibleQuantity": {
"@type": "QuantitativeValue",
"minValue": 1
}
},
"additionalProperty": {
"@type": "PropertyValue",
"name": "Free Shipping Eligibility",
"value": "Free for VIP members and orders over $75"
}
}
Related: Customer reviews and AI agent impact · Shopify B2C wholesale pricing · Shopify metafields and AI agents
Frequently asked questions
Is there a schema.org type for loyalty programs?
Schema.org has ProgramMembership for describing membership in a loyalty program. For product-level loyalty signals (points per purchase, member price), use Offer.additionalProperty with PropertyValue entities — there is no native "points" Offer property.
Do AI shopping agents surface loyalty program benefits?
Yes, for loyalty-intent queries. If benefits are in structured data, AI agents can cite them. If they're only in JavaScript widgets, AI crawlers see nothing and your loyalty program is invisible to AI-powered search.
How do Shopify loyalty apps affect structured data?
All major loyalty apps (Smile.io, Yotpo, LoyaltyLion) render via JavaScript widgets that AI crawlers cannot read. You must output loyalty data server-side via Liquid using metafields populated from the app's API.
How do I show member pricing in Shopify Product schema?
Output a second Offer entity with a description explaining the membership requirement, and optionally set eligibleCustomerType. Store the member price in a product metafield and pull it with Liquid.