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.
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
<p>Best before date printed on bottom of bottle. All orders ship with 18+ months of shelf life remaining.</p> // Invisible as structured data
{
"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
- Create freshness metafield namespace in Shopify Admin:
shelf_life_months,current_batch_expiry,period_after_opening,storage_conditions - Fill
shelf_life_monthsfor all perishable products (supplements, food, cosmetics) - Fill
current_batch_expiryfor batch-tracked products and establish a process to update on each stock receipt - Add
period_after_openingfor all leave-on cosmetics and opened-container supplements - Add freshness additionalProperty block in
product.liquidJSON-LD, driven by metafields - Use Liquid null-guard so properties are only output when metafield is populated
- Format batch expiry dates as ISO 8601 (YYYY-MM-DD) using Liquid
datefilter - Add
storageConditionsfor products that require specific storage (refrigeration, UV protection) - Set
indefiniteShelfLife: truefor genuinely non-expiring products (raw honey, salt) alongside a quality best-before date - Run CatalogScan to verify freshness signals appear in your AI readiness score
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.