SEO Guide · 2026
Shopify POS and Online Store Structured Data for AI Shopping Agents (2026)
Shopify merchants with both physical stores and an online presence face a structured data gap that costs them local AI recommendations: Shopify POS never adds LocalBusiness schema or in-store availability signals to your product pages. This guide covers everything from LocalBusiness JSON-LD to BOPIS to multi-location inventory.
LocalBusiness schema on your locations page, add availableAtOrFrom to your product Offer objects, and encode BOPIS availability as a separate Offer with pickup-specific signals. Use the Shopify Locations API in Liquid to generate this per-product.
The POS structured data gap
When Shopify merchants set up POS, they enter location data — address, phone, hours — into Shopify Admin under Settings → Locations. This data powers inventory allocation, label printing, and order routing. It does not affect the online store's HTML or JSON-LD in any way.
The result is that a merchant with a physical store in Brooklyn selling a product that a Brooklyn shopper is searching for right now gets no advantage from AI agents — because the AI agent has no structured evidence that the product is available at a Brooklyn store, what the store's address is, or whether pickup is available today. The merchant's online Shopify store looks, to the agent, identical to a store that ships only from a warehouse in Nevada.
The structured data signals that fix this are well-defined in schema.org. The challenge is that Shopify's default theme layer doesn't generate any of them:
LocalBusinessschema with address, geo, and opening hours — goes on your Locations page and referenced from product Offer objectsavailableAtOrFromon your Offer — points to the Store LocalBusiness to signal in-store availability- BOPIS Offer — a separate Offer entry for in-store pickup with a zero shipping rate and your store address as the pickup point
LocalBusiness schema for your Shopify POS location
Add this JSON-LD to your Locations or Contact page (and reference the same @id from product pages):
{
"@context": "https://schema.org",
"@type": ["LocalBusiness", "Store"],
"@id": "https://yourstore.com/#location-brooklyn",
"name": "Your Store — Brooklyn",
"description": "Our Brooklyn flagship store, open Monday–Saturday 10am–7pm.",
"url": "https://yourstore.com/locations/brooklyn",
"telephone": "+1-718-555-0123",
"email": "brooklyn@yourstore.com",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Smith Street",
"addressLocality": "Brooklyn",
"addressRegion": "NY",
"postalCode": "11201",
"addressCountry": "US"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 40.6892,
"longitude": -73.9887
},
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "10:00",
"closes": "19:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday",
"opens": "10:00",
"closes": "18:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": "Sunday",
"opens": "11:00",
"closes": "17:00"
}
],
"priceRange": "$$",
"image": "https://yourstore.com/assets/store-brooklyn.jpg",
"hasMap": "https://maps.google.com/?q=123+Smith+Street+Brooklyn+NY"
}
Product availability: online + in-store with BOPIS
For products available both online (shipping) and in-store (pickup), use an array of Offer objects — one for online purchase with shipping, one for BOPIS with a pickup location. AI agents parse both and can surface either or both depending on the shopper's query context.
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Your Product",
"offers": [
{
"@type": "Offer",
"name": "Buy online — ships to you",
"price": "49.99",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"url": "https://yourstore.com/products/your-product",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "4.99",
"currency": "USD"
},
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"transitTime": {
"@type": "QuantitativeValue",
"minValue": 3,
"maxValue": 5,
"unitCode": "DAY"
}
},
"shippingDestination": {
"@type": "DefinedRegion",
"addressCountry": "US"
}
}
},
{
"@type": "Offer",
"name": "Pick up in store — Brooklyn",
"price": "49.99",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"availableAtOrFrom": {
"@id": "https://yourstore.com/#location-brooklyn"
},
"availabilityStarts": "2026-06-02",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "0",
"currency": "USD"
},
"doesNotShip": false,
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": {
"@type": "QuantitativeValue",
"minValue": 0,
"maxValue": 0,
"unitCode": "DAY"
},
"transitTime": {
"@type": "QuantitativeValue",
"minValue": 0,
"maxValue": 0,
"unitCode": "DAY"
}
}
},
"potentialAction": {
"@type": "BuyAction",
"actionStatus": "PotentialActionStatus",
"target": "https://yourstore.com/products/your-product",
"description": "Reserve online, pick up today at our Brooklyn store"
}
}
]
}
AI agent behavior: local availability queries
| Query type | Without POS schema | With LocalBusiness + availableAtOrFrom |
|---|---|---|
| "buy [product] near me" | Only national e-commerce results shown; your store excluded | Your store surfaced as a local purchase option with address and distance |
| "pick up today in Brooklyn" | No local store data; agent cannot confirm pickup availability | BOPIS Offer surfaces your store; agent confirms same-day pickup possible |
| "[product] in stock locally" | No inventory signal for physical locations | InStock signal on availableAtOrFrom Offer confirms local availability |
| "[product] store hours" | Agent directs to Google Business Profile (not your website) | openingHoursSpecification on LocalBusiness answers the query from your site |
| "cheapest way to get [product]" | Agent compares online shipping costs only | Agent includes free in-store pickup as a cost-$0 delivery option |
Shopify Liquid: generating per-location Offer objects
For merchants with multiple POS locations, store location data as JSON in a global section or metaobject, then loop over it in Liquid to generate Offer objects:
{% comment %}
Store location data in a shopify metaobject or global section.
Each location: name, id_slug, address, city, state, zip, lat, lng,
opens_weekday, closes_weekday, opens_weekend, closes_weekend
{% endcomment %}
{% assign locations_with_stock = '' %}
{% for location in shop.metafields.custom.pos_locations.value %}
{% assign location_inventory = product.variants.first.inventory_quantity %}
{% comment %} In a real implementation, use Inventory API data per location {% endcomment %}
{% if location_inventory > 0 %}
{% assign locations_with_stock = locations_with_stock | append: location.id_slug | append: ',' %}
{% endif %}
{% endfor %}
"offers": [
{
"@type": "Offer",
"name": "Buy online",
"price": "{{ product.price | divided_by: 100.0 }}",
"priceCurrency": {{ shop.currency | json }},
"availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
"url": {{ shop.url | append: product.url | json }}
}
{% for location in shop.metafields.custom.pos_locations.value %}
,{
"@type": "Offer",
"name": "Pick up at {{ location.name }}",
"price": "{{ product.price | divided_by: 100.0 }}",
"priceCurrency": {{ shop.currency | json }},
"availability": "https://schema.org/InStock",
"availableAtOrFrom": {
"@id": {{ shop.url | append: '/#location-' | append: location.id_slug | json }}
},
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": { "@type": "MonetaryAmount", "value": "0", "currency": {{ shop.currency | json }} }
}
}
{% endfor %}
]
Google AI Mode and local inventory
Google AI Mode has a specific display format for local shopping that activates when structured data includes availableAtOrFrom with geo coordinates. This format shows a map pin with your store name, distance from the user's location (inferred from their search context or browser geo), current availability, and hours. This display is only available to stores with:
- A
LocalBusinessschema on any page of the site (not just the product page) - A product-level
OfferwithavailableAtOrFrompointing to that LocalBusiness via@id - Valid geo coordinates (
latitude/longitude) on the LocalBusiness - Current
openingHoursSpecification(outdated hours suppress the display)
Maintaining accurate hours is critical — Google AI Mode cross-references your structured data hours against your Google Business Profile. If they conflict, the structured data display may be suppressed. Update your JSON-LD hours whenever you change your Shopify location hours.
InStoreOnly vs InStock: which to use
| Scenario | Correct availability value | Why |
|---|---|---|
| Product sold online + in-store | InStock on online Offer; InStock on BOPIS Offer |
Both purchase paths are available; use separate Offer objects, not InStoreOnly |
| Product sold only in physical store (not online) | InStoreOnly |
Signals to AI agents that this cannot be shipped; directs to store visit |
| Product out of stock online, in stock in-store | OutOfStock on online Offer; InStock on BOPIS Offer |
Separate Offers per channel allow different availability per fulfillment method |
| Online only (no POS) | InStock on single Offer; no availableAtOrFrom |
No BOPIS Offer needed; do not add empty availableAtOrFrom |
Frequently asked questions
How do AI shopping agents handle buy online, pick up in store?
AI agents handle BOPIS by reading the availableAtOrFrom property on your Offer schema, which points to a LocalBusiness with the store's address and hours. Without this, agents can't match "pick up today" or "available at store near me" queries. With correct schema — Offer with InStock for online, a BOPIS Offer with availableAtOrFrom, and a Store LocalBusiness with geo coordinates — agents surface your product for local purchase queries from shoppers near your store.
What is the difference between InStock and InStoreOnly?
InStock means the product is available for online purchase and shipping. InStoreOnly means it can only be purchased in person — not online. For Shopify POS stores selling both ways, use InStock on the online Offer and a separate BOPIS Offer with availableAtOrFrom. Avoid InStoreOnly for products that also ship — it causes AI agents to exclude the product from online shopping recommendations.
Does Shopify automatically add LocalBusiness structured data for POS locations?
No. Shopify POS location data in the admin — address, phone, hours — never reaches your product page JSON-LD. To surface physical locations in AI shopping results, you must add LocalBusiness schema manually to your theme. Add it to a dedicated Locations page, and reference it from product pages via availableAtOrFrom on Offer objects.
How do I show multi-location inventory in product structured data?
Use multiple Offer objects — one per location — with each Offer's availableAtOrFrom pointing to a Store LocalBusiness for that location. Set availability to InStock or OutOfStock per location based on real inventory. For many-location stores, limit to your top 3–5 locations to avoid bloating page size. Use Shopify's Inventory API data read via Liquid to generate accurate per-location availability dynamically.
Check your POS and local availability structured data
CatalogScan scans for missing LocalBusiness schema, incomplete availableAtOrFrom signals, and BOPIS structured data gaps across your product catalog.
Scan your store free