Optimization Guide

Shopify Product Expiry and Best-Before Date Structured Data for AI Shopping Agents

AI shopping agents answer "supplements with at least 18 months shelf life" and "fresh protein powder" by reading structured data — not your freshness guarantee copy. Shopify's default JSON-LD includes zero expiry or freshness signals, leaving food, supplement, and cosmetics stores invisible in shelf-life filter queries.

TL;DR Add additionalProperty to your Product with propertyID: "bestBeforeDate" (ISO 8601 date) for fixed-expiry products, or propertyID: "shelfLifeMonths" for shelf-life guarantee policies. Store expiry dates in Shopify metafields (inventory.current_batch_expiry) and inject conditionally in product.liquid. Include a minimumShelfLifeDays guarantee alongside the batch-specific date so AI agents understand both the current batch and your freshness policy. Never hardcode a specific expiry date — drive it from metafields.

The Freshness Visibility Gap

Freshness and shelf life are critical purchase-decision signals for food, supplement, and cosmetics buyers. Questions like "how long will this last before I need to reorder?" and "will this expire before I finish it?" directly influence conversion. Yet in virtually every Shopify supplement and food store, shelf-life information lives in one of three places: product description paragraph, FAQ tab, or footnote copy — none of which AI shopping agents can parse in a structured way.

When ChatGPT or Perplexity answers "whey protein with longest shelf life" or "skincare products with at least 12 months before expiry," they are querying structured freshness signals — not crawling page text. Stores without bestBeforeDate or shelfLifeMonths additionalProperty are invisible to these queries, even if they explicitly guarantee fresh stock on every page.

Freshness query types that require structured signals

Query type Example Required signal Signal location
Minimum shelf life filter "supplements with 18+ months shelf life" shelfLifeMonths: 24 Product additionalProperty
Not expiring soon "protein powder not expiring for a year" bestBeforeDate: 2027-06-01 Product additionalProperty (batch-driven)
Freshness guarantee "fresh collagen guaranteed when shipped" minimumShelfLifeDays: 365 Product or Offer additionalProperty
Best-by vs. use-by distinction "vitamins that are safe after best-before" bestBeforeDate vs. expiryDate label additionalProperty with matching propertyID
Cosmetics period-after-opening "moisturizer good for 12 months after opening" periodAfterOpeningMonths: 12 Product additionalProperty

Without vs. with freshness structured data

Description text only (AI agent can't parse)
<p>Best before date printed
on bottom of bottle.
All orders ship with 18+
months of shelf life
remaining.</p>
// Invisible as structured data
Structured freshness signal (AI agent parseable)
{
  "additionalProperty": [{
    "@type": "PropertyValue",
    "propertyID":
      "shelfLifeMonths",
    "value": "24",
    "description":
      "Ships with 24+ months
      of shelf life remaining"
  }]
}

Freshness JSON-LD Patterns

Minimum viable shelf-life guarantee

The shelfLifeMonths additionalProperty declares your freshness policy — how many months of shelf life customers are guaranteed to receive. This is the most durable signal because it doesn't need updating per batch:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Grass-Fed Whey Protein — Vanilla — 2lb",
  "additionalProperty": [
    {
      "@type": "PropertyValue",
      "propertyID": "shelfLifeMonths",
      "name": "Guaranteed shelf life",
      "value": "18",
      "description": "All orders ship with a minimum of 18 months of shelf life remaining from date of purchase"
    },
    {
      "@type": "PropertyValue",
      "propertyID": "storageConditions",
      "value": "Store in a cool, dry place below 25°C. Refrigerate after opening."
    }
  ]
}

Batch-specific best-before date

For products where you know and can guarantee the current batch expiry, declare it explicitly. This enables AI agents to answer "will this expire before I finish it?" queries with confidence:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Magnesium Glycinate — 120 Capsules",
  "additionalProperty": [
    {
      "@type": "PropertyValue",
      "propertyID": "bestBeforeDate",
      "name": "Best before",
      "value": "2027-09-30",
      "description": "Current batch best before date — ISO 8601 format"
    },
    {
      "@type": "PropertyValue",
      "propertyID": "shelfLifeMonths",
      "name": "Minimum shelf life on dispatch",
      "value": "18"
    }
  ]
}

Cosmetics period-after-opening (PAO)

For skincare and cosmetics, the period-after-opening (PAO) is the relevant freshness signal — how many months the product remains effective after first use. Declare it alongside the manufacture or best-before date:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Vitamin C Serum — 1oz",
  "additionalProperty": [
    {
      "@type": "PropertyValue",
      "propertyID": "periodAfterOpeningMonths",
      "name": "Period after opening",
      "value": "12",
      "description": "Use within 12 months of opening for best results — the 12M symbol on packaging"
    },
    {
      "@type": "PropertyValue",
      "propertyID": "shelfLifeMonths",
      "name": "Unopened shelf life",
      "value": "24"
    },
    {
      "@type": "PropertyValue",
      "propertyID": "storageConditions",
      "value": "Store away from direct sunlight and heat. Refrigerate after opening to extend potency."
    }
  ]
}

Food product with best-before and use-by distinction

For food products, include both the best-before date (quality peak) and whether the product is safe to consume beyond it. Use expiryDate only when the product genuinely should not be consumed after that date:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Organic Raw Honey — 12oz",
  "additionalProperty": [
    {
      "@type": "PropertyValue",
      "propertyID": "bestBeforeDate",
      "name": "Best before",
      "value": "2028-12-31",
      "description": "Best before date — raw honey has indefinite shelf life when stored properly; this date indicates optimal flavor quality"
    },
    {
      "@type": "PropertyValue",
      "propertyID": "indefiniteShelfLife",
      "value": "true",
      "description": "Raw honey does not expire — archaeologists have found 3000-year-old honey still edible"
    },
    {
      "@type": "PropertyValue",
      "propertyID": "storageConditions",
      "value": "Store at room temperature, away from direct sunlight. Do not refrigerate."
    }
  ]
}

Shopify Liquid Implementation

Create freshness metafields in Shopify Admin → Settings → Custom data → Products. Use two types of signals: policy-level (the shelf-life guarantee, set once per product) and batch-level (the current expiry date, updated when new stock arrives):

Metafield key Type Update frequency Maps to
freshness.shelf_life_months Integer Once (policy) shelfLifeMonths additionalProperty
freshness.current_batch_expiry Date Per batch receipt bestBeforeDate additionalProperty
freshness.period_after_opening Integer (months) Once (cosmetics) periodAfterOpeningMonths additionalProperty
freshness.storage_conditions Single-line text Once storageConditions additionalProperty
freshness.indefinite_shelf_life Boolean Once (honey, salt, etc.) indefiniteShelfLife additionalProperty
{% assign shelf_life = product.metafields.freshness.shelf_life_months %}
{% assign batch_expiry = product.metafields.freshness.current_batch_expiry %}
{% assign pao = product.metafields.freshness.period_after_opening %}
{% assign storage = product.metafields.freshness.storage_conditions %}
{% assign indefinite = product.metafields.freshness.indefinite_shelf_life %}

{% if shelf_life or batch_expiry or pao %}
"additionalProperty": [
  {% if shelf_life %}
  {
    "@type": "PropertyValue",
    "propertyID": "shelfLifeMonths",
    "name": "Minimum shelf life on dispatch",
    "value": "{{ shelf_life }}"
  },
  {% endif %}
  {% if batch_expiry %}
  {
    "@type": "PropertyValue",
    "propertyID": "bestBeforeDate",
    "name": "Best before (current batch)",
    "value": "{{ batch_expiry | date: '%Y-%m-%d' }}"
  },
  {% endif %}
  {% if pao %}
  {
    "@type": "PropertyValue",
    "propertyID": "periodAfterOpeningMonths",
    "name": "Period after opening",
    "value": "{{ pao }}"
  },
  {% endif %}
  {% if indefinite %}
  {
    "@type": "PropertyValue",
    "propertyID": "indefiniteShelfLife",
    "value": "true"
  },
  {% endif %}
  {% if storage %}
  {
    "@type": "PropertyValue",
    "propertyID": "storageConditions",
    "value": {{ storage | json }}
  }
  {% endif %}
],
{% endif %}

Freshness Signal Reference by Category

Product category Primary signal Secondary signal Typical shelf life
Supplements (capsules, tablets) bestBeforeDate shelfLifeMonths 18–36 months from manufacture
Protein powder bestBeforeDate shelfLifeMonths 12–24 months; 6 months after opening
Skincare / serums periodAfterOpeningMonths shelfLifeMonths (unopened) PAO: 6–24 months; unopened: 24–36 months
Food (shelf-stable) bestBeforeDate storageConditions Varies widely; 6 months to several years
Raw honey / salt / sugar indefiniteShelfLife: true bestBeforeDate (quality, not safety) Indefinite with proper storage

Common Mistakes

Mistake Impact Fix
Shelf life in description text only AI agents cannot parse "18 months shelf life" from unstructured text Add shelfLifeMonths additionalProperty alongside description text
Hardcoding a specific batch expiry date in template When new stock arrives, every product page shows wrong date until next deploy Drive bestBeforeDate from metafield freshness.current_batch_expiry
Using expiryDate for quality-peak items Signals the product is unsafe after that date — creates incorrect customer expectation Use bestBeforeDate for quality-peak; reserve expiryDate for genuine safety expiry
Missing periodAfterOpeningMonths for cosmetics AI agents cannot answer PAO queries — key for skincare discovery Add PAO in additionalProperty for all leave-on skincare products
No storageConditions for products requiring specific storage AI agents cannot surface products in "fridge-free supplements" or "no refrigeration needed" queries Add storageConditions additionalProperty describing storage requirements

Implementation Checklist

Frequently Asked Questions

How do I declare a product expiry date in Shopify structured data?

Add additionalProperty to your Product with propertyID: "bestBeforeDate" and an ISO 8601 date value (e.g., "2027-09-30"). For products without batch-specific dates, use propertyID: "shelfLifeMonths" with the guaranteed shelf life in months as the value. Store expiry dates in Shopify metafields and inject via Liquid conditionally — never hardcode a date in the template.

Can AI shopping agents filter by shelf life?

Yes. ChatGPT Shopping and Perplexity Shopping parse bestBeforeDate and shelfLifeMonths additionalProperty to answer "supplements with 18+ months shelf life" and "protein powder not expiring for a year" queries. Stores without freshness structured data cannot appear in these results even if they guarantee fresh stock on every product page.

What is the difference between bestBefore and expiryDate?

"Best before" indicates quality peak — the product is generally safe beyond this date but may not be at optimal quality (relevant for most supplements and packaged food). "Expiry date" or "use by" indicates safety — the product should not be consumed after this date. Use bestBeforeDate for quality-peak items (vitamins, protein powder), and expiryDate only for genuine safety expiry (some pharmaceuticals, perishable foods). AI agents trained on food safety data apply this distinction.

How do I declare shelf life in months rather than a specific date?

Use propertyID: "shelfLifeMonths" with a numeric string value describing the guaranteed shelf life from dispatch. This is preferable to a batch-specific date for products where each stock receipt has a different expiry — the structured data describes your policy, not a batch date. For example: "value": "24" declares customers receive products with at least 24 months remaining. Combine with a batch-specific bestBeforeDate metafield for the current lot when available.

How should I handle products with batch-variable expiry dates?

Store the current batch expiry in a product metafield (freshness.current_batch_expiry) and inject it into JSON-LD via Liquid. Include a null-guard so the property is omitted if the metafield is empty. Update the metafield whenever new stock arrives. Also include a shelfLifeMonths guarantee so AI agents understand your freshness policy independent of the current batch date.

Related Resources