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.

TL;DR Every major Shopify loyalty app (Smile.io, Yotpo, LoyaltyLion) renders benefits via client-side JavaScript. AI crawlers see none of it. Fix: store loyalty data in metafields, then output it server-side in Liquid as 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

AppRenders viaAdds JSON-LD?AI-agent readable?
Smile.ioClient-side JS widgetNoNo
Yotpo Loyalty & ReferralsClient-side JS widgetNoNo
LoyaltyLionClient-side JS widgetNoNo
Stamped LoyaltyClient-side JS widgetNoNo
RivoClient-side JS widgetNoNo
GrowaveClient-side JS widgetNoNo
Custom Liquid implementationServer-side LiquidYes (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 typeNo loyalty schemaWith loyalty schema
"Best rewards for [product category]"Store not surfaced in loyalty-aware comparisonsBrand may appear with points benefit cited
"[Brand] loyalty program review"AI must rely on external blog postsProgramMembership data used directly in answer
"How many points do I earn at [brand]?"No structured answer availableAI reads additionalProperty earn rate
"[Brand] member discount"AI has no structured price dataSecond Offer with member price is readable
"Stores with free shipping for members"Not surfacedMerchantReturnPolicy + 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:

  1. Create metafield definition: loyalty.points_earn_rate (type: number_integer) at the product level
  2. Use the app's webhook (order created/fulfilled) to calculate points and write to metafield via Shopify Admin API
  3. In your product Liquid template, read product.metafields.loyalty.points_earn_rate and 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.