# Offer schema: price and availability markup

> What the Schema.org Offer type is, how to write price, currency and availability, what Google requires, and how Offer differs from AggregateOffer.

URL: https://clione.ai/glossary/offer-schema/
Lang: en

In the markup of a [product page](/glossary/product-page/), Offer answers two questions the product does not: **what it sells for and whether you can buy it today**. It is a type from [Schema.org](/glossary/schema-org/), and its name is a vocabulary term, not a plain word. It has nothing to do with an offer in the sense of a discount: a shirt at its regular price has an Offer too. And it is not the product, which has its own type, [Product](/glossary/product-schema/). It matters because it is the part of your [structured data](/glossary/structured-data/) Google needs to show the price in [rich results](/glossary/rich-results/), and the part that drifts most often: every markdown and every stock change leaves it stale unless it is updated at the same time as the page.

## How it works

Schema.org defines Offer as an offer to transfer some rights to an item or to provide a service, with examples such as selling tickets, renting a movie, repairing a motorcycle or lending a book. Unless something else is stated, **an Offer is understood as an offer to sell**.[^schema-org-offer] On a product page, the product carries it inside the `offers` property.

The properties that matter in a store, according to Schema.org:[^schema-org-offer]

| Property                  | What it says                                                                             |
| ------------------------- | ---------------------------------------------------------------------------------------- |
| `price`                   | The price. With a period as the decimal separator, never a comma, and digits from 0 to 9 |
| `priceCurrency`           | The currency, in ISO 4217 format, such as `USD` or `EUR`                                 |
| `availability`            | Availability: in stock, out of stock, preorder                                           |
| `itemCondition`           | The condition of the item, for example new or used                                       |
| `priceValidUntil`         | The date after which the price is no longer available                                    |
| `seller`                  | Who is selling: an organization or a person                                              |
| `shippingDetails`         | The shipping terms                                                                       |
| `hasMerchantReturnPolicy` | The [return policy](/glossary/return-policy/) that applies                               |

Google takes that vocabulary and sets its own minimums. For [merchant listings](/glossary/merchant-listings/), **the offer has to carry a price above zero and the currency as a three-letter ISO 4217 code**. It also recommends availability, with values such as `https://schema.org/InStock`, `OutOfStock`, `BackOrder` or `PreOrder`; the condition, such as `https://schema.org/NewCondition`; the price validity date; shipping details; the return policy, with the [`MerchantReturnPolicy`](/glossary/merchantreturnpolicy/) type; and the URL the item can be bought from.[^google-merchant-listing]

There is one more condition that is not in the vocabulary. Merchant listings **accept only Offer, not [AggregateOffer](/glossary/aggregateoffer/)**, because to qualify the merchant has to be the one selling the product. Product snippets, by contrast, accept both.[^google-merchant-listing] For them the price is required if you use Offer, the currency is recommended, and Google warns that the snippet may not show if `priceValidUntil` is a date in the past.[^google-product-snippet]

And one warning that applies almost only to Offer. Google can read structured data added with JavaScript, but it warns that **dynamically generated markup can make Shopping crawls less frequent and less reliable, a problem for things that change fast, like [price and availability](/glossary/price-and-availability/)**.[^google-js-sd]

## In a store

The offer for a shirt on sale, with what the page shows and how it is written in the markup. **Fictional example**: the store, the product, the price and the dates are made up.

| What the page shows            | How it is written in the offer                                                                                                                    |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| $29.90 (was $39.90)            | `"price": "29.90"` and `"priceCurrency": "USD"` |
| "In stock"                     | `"availability": "https://schema.org/InStock"`                                                                                                    |
| "New"                          | `"itemCondition": "https://schema.org/NewCondition"`                                                                                              |
| "Price valid until October 31" | `"priceValidUntil": "2026-10-31"`                                                                                                                 |

```json
"offers": {
  "@type": "Offer",
  "price": "29.90",
  "priceCurrency": "USD",
  "availability": "https://schema.org/InStock",
  "itemCondition": "https://schema.org/NewCondition",
  "priceValidUntil": "2026-10-31",
  "url": "https://www.example.com/calella-linen-shirt"
}
```

Three things the example shows. The price in the offer is the one the customer pays today, the discounted one, because the data has to represent the page as it is.[^google-sd-policies] The validity date is only set if the page states one; here the sale has an announced end. And availability is an address in the vocabulary, not the phrase the customer reads: "Only a few left" or "Ships in 48 hours" have no equivalent value, so you mark the fact (it is in stock) and leave the rest on the page.


Writing the price the way it looks on the page, "$29.90" or "29,90 €", inside `price`. Schema.org asks for a period as the decimal separator and the currency in its own property, with its ISO 4217 code.[^schema-org-offer]


## How to check it

1. **Search for `offers` in the page source of the product page** and note the price, the currency and the availability it declares.
2. **Compare them with what a customer sees at that moment**, sale price included. If they do not match, the data does not represent the page.[^google-sd-policies]
3. **Look at the `priceValidUntil` date**, if there is one. If it has passed, the product snippet may not show.[^google-product-snippet]
4. **Check whether the offer is in the HTML the server sends** or whether JavaScript adds it afterwards: for price and availability, Google warns that the second makes Shopping crawls less frequent and less reliable.[^google-js-sd]
5. **Run the URL through the [Rich Results Test](/glossary/rich-results-test/)** to see which experiences it detects and with what errors.[^rich-results-test]


## Related concept: AggregateOffer

AggregateOffer is the type Schema.org proposes **when one product has several offers**, for example the same pair of shoes sold by different shops, and it sums up the set with the lowest price (`lowPrice`), the highest (`highPrice`) and the number of offers (`offerCount`).[^schema-org-aggregateoffer] It fits a comparison site or a marketplace; on the product page of a store that sells its own goods, Offer is the right type, and Google accepts only Offer for merchant listings.[^google-merchant-listing]

To keep going, [the structured data reading path](/learn/structured-data/) puts the other terms in order, and the [JSON-LD](/glossary/json-ld/) entry shows where this offer sits inside a product page's block.
