Skip to content

Proposal to add support for explicit identification of price categories such as sale price #2712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
alex-jansen opened this issue Sep 17, 2020 · 30 comments · Fixed by #2716
Closed
Assignees

Comments

@alex-jansen
Copy link
Contributor

Context - This is a proposal from Google based on our experience consuming schema.org Offer markup and working with similar data from online merchants. If it were accepted, it would make it easier for us and others to understand the different prices related to an offer.

Introduction

Many products advertised and sold on the web have different categories of prices, for example:

  • invoice price,
  • list price,
  • sale price,
  • manufacturer-suggested retail price (MSRP)
  • minimum advertised price (MAP)
  • price by standardized weight or volume

Currently, schema.org does not provide a structured way of differentiating the categories of prices associated with an offer. The closest option is the /priceType property, but this field is intended to identify different price components, for example cleaning fee, service fee, downpayment, monthly installment payment, recycling fee, airport surcharge, etc. Several price components are combined together to make up a single price category.

See also issues #829 and more recently #2689 for a discussion on Compound pricing.

Proposal

We believe allowing an explicit and structured way of expressing the category of a /priceSpecification would benefit the ecosystem.

This design builds on the approach explored in #2689 which introduced an enumeration (e.g. /PriceTypeEnumeration) for the existing /priceType property. Alongside this we propose another enumeration (e.g., _/PriceCategoryEnumeration) _for different categories of prices, and a property /priceCategory to reference these. Example 2 below shows both of these dimensions being used in a single scenario.

New property /priceCategory would then be added to types /UnitPriceSpecification and /CompoundPriceSpecification to allow specification of a price category for singular prices as well as compound prices.

Example 1

The following example illustrates how to specify three different price categories (MSRP, list price, and sale price) for an offer using the proposed new enumeration

{
  "@context": "https://schema.org/",
  "@type": "Product",
  "sku": "coffeemaker-12345",
  "image": "https://www.example.com/coffeemaker.jpg",
  "name": "Coffee maker",
  "description": "Fast coffee maker, large",
  "gtin14": "12345678905678",
  "brand": {
    "@type": "Thing",
    "name": "SomeCoffeeBrand"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://www.example.com/coffeemaker",
    "itemCondition": "https://schema.org/NewCondition",
    "availability": "https://schema.org/InStock",
    "priceSpecification": [
      {
          "@type": "UnitPriceSpecification",
          "priceCategory": "https://schema.org/MSRP",
          "price": 150.00,
          "priceCurrency": "EUR"
      },
      {
          "@type": "UnitPriceSpecification",
          "priceCategory": "https://schema.org/ListPrice",
          "price": 119.95,
          "priceCurrency": "EUR"
      },
      {
          "@type": "UnitPriceSpecification",
          "priceCategory": "https://schema.org/SalePrice",
          "price": 99.95,
          "priceCurrency": "EUR",
          "validFrom": "2020-10-01",
          "validThrough": "2020-10-14"
      }
      ]
    }
  }
}

Note that the sale price specification in this example has an explicit date range to indicate when the sale price is valid. Also note the absence of properties /price and /priceCurrency at the offer level.

Example 2

The following example, building on the example given in issue #2689, illustrates a combination of price categories and price types.

{
  "@context": "https://schema.org/",
  "@type": "Product",
  "sku": "GZDU23L/B",
  "image": "https://www.example.com/phone_5_32_slvr.jpg",
  "name": "Phone 5" 32GB Silver",
  "description": "New phone 5" screen in silver with 32GB + contract",
  "gtin14": "00821793049157",
  "mpn": "G-2PW4100-021-B",
  "brand": {
    "@type": "Thing",
    "name": "PhoneBrand"
  },
  "color": "Silver",
  "offers": {
    "@type": "Offer",
    "url": "http://www.example.com/GZDU23LB",
    "itemCondition": "https://schema.org/NewCondition",
    "availability": "https://schema.org/InStock",
    "priceSpecification": [
      {
        "@type": "CompoundPriceSpecification",
        "priceComponent": [
          {
            "@type": "UnitPriceSpecification",
            "priceType": "https://schema.org/Downpayment",
            "price": 215,
            "priceCurrency": "GBP"
          },
          {
            "@type": "UnitPriceSpecification",
            "priceType": "https://schema.org/Installment",
            "price": 38.65,
            "priceCurrency": "GBP",
            "billingIncrement": 1,
            "unitCode": "MON",
            "eligibleQuantity": {
              "@type": "QuantitativeValue",
              "value": 12,
              "unitCode": "MON"
            }
          },
          {
            "@type": "UnitPriceSpecification",
            "priceType": "https://schema.org/Subscription",
            "price": 15,
            "priceCurrency": "GBP",
            "billingIncrement": 1,
            "unitCode": "MON",
            "eligibleQuantity": {
              "@type": "QuantitativeValue",
              "minValue": 24,
              "unitCode": "MON"
            }
          }
        ]
      },
      {
        "@type": "CompoundPriceSpecification",
        "priceCategory": "https://schema.org/SalePrice",
        "validFrom": "2020-10-01",
        "validThrough": "2020-10-14",
        "priceComponent": [
          {
            "@type": "UnitPriceSpecification",
            "priceType": "https://schema.org/Downpayment",
            "price": 0,
            "priceCurrency": "GBP"
          },
          {
            "@type": "UnitPriceSpecification",
            "priceType": "https://schema.org/Installment",
            "price": 29.99,
            "priceCurrency": "GBP",
            "billingIncrement": 1,
            "unitCode": "MON",
            "eligibleQuantity": {
              "@type": "QuantitativeValue",
              "value": 12,
              "unitCode": "MON"
            }
          },
          {
            "@type": "UnitPriceSpecification",
            "priceType": "https://schema.org/Subscription",
            "price": 15,
            "priceCurrency": "GBP",
            "billingIncrement": 1,
            "unitCode": "MON",
            "eligibleQuantity": {
              "@type": "QuantitativeValue",
              "minValue": 24,
              "unitCode": "MON"
            }
          }
        ]
      }
    ]
  }
}

This example builds on the example in #2689 by adding a second compound sale price. The difference in this example between the default price (which comes without the /priceCategory property) and the sale price are the downpayment and installment price components. The subscription price component is unchanged.

Note that this example has modified the example in #2689 by moving the down payment under /CompoundPriceSpecification.

Consideration

Different price categories can be used under different circumstances, for example a product sold on the web could have a different sales price than a product advertised on the web. We could consider adding yet another pricing dimension to model how the price should be used, but this seems overkill and can instead be modeled by adding more enumeration values to the proposed /PriceCategoryEnumeration type.

@jvandriel
Copy link

jvandriel commented Sep 17, 2020

"Also note the absence of properties /price and /priceCurrency at the offer level."

In those cases where the /priceCurrency has the same value for all UnitPriceSpecifications wouldn't it suffice to specify it directly for the /Offer?

Seems a bit overkill to me having to specify the same value over and over again within a single /Offer.

@alex-jansen
Copy link
Contributor Author

@jvandriel Agreed that should be an option, since I would typically expect all prices to be in the same currency.

@jvandriel
Copy link

jvandriel commented Sep 17, 2020

In the comments for the second example it's mentioned that

"the default price (which comes without the /priceCategory property)"

yet in the first example https://schema.org/ListPrice has been specified as value for /priceCategory.

Aren't a /ListPrice and "default price" the same thing? And if so, shouldn't it be either one or the other (as in, you either specify /ListPrice OR you don't specify the /priceCategory property to set the standard price as opposed to both being an option)?

@alex-jansen
Copy link
Contributor Author

@jvandriel Yes, they are the same thing, I wanted to give both examples to start a discussion (I guess I succeeded :) ) on the pros and cons of absence of a /priceCategory implying that the price is the standard (or list) price, or always requiring the explicit specification of /priceCategory if there are multiple prices.

@jvandriel
Copy link

jvandriel commented Sep 17, 2020

Well if it's up for discussion... ;)

Although I like the the idea of using /ListPrice (mostly for debugging purposes during development), personally I feel it'd be best if we try to prevent having to specify it for every single /Offer. More or less the same way as was done by having http://purl.org/goodrelations/v1#Sell be the default value for /businessFunction (discussed in 2348 and #2357).

For example, I'd hate to have to write/publish the following:

{
  "@context":"https://schema.org",
  "@type": "Product",
  "sku": "GZDU23L\/B",
  "image": "https://www.example.com/phone_5_32_slvr.jpg",
  "name": "Phone 5\" 32GB Silver",
  "description": "New phone 5\" screen in silver with 32GB + contract",
  "gtin14": "00821793049157",
  "mpn": "G-2PW4100-021-B",
  "brand": {
    "@type": "Thing",
    "name": "PhoneBrand"
  },
  "color": "Silver",
  "offers": {
    "@type": "Offer",
    "url": "http://www.example.com/GZDU23LB",
    "itemCondition": "https://schema.org/NewCondition",
    "availability": "https://schema.org/InStock",
    "price": 999,
    "priceCurrency": "GBP",
    "priceSpecification": {
      "@type": "UnitPriceSpecification",
      "priceCategory": "https://schema.org/ListPrice"
    }
  }
}

@alex-jansen
Copy link
Contributor Author

@jvandriel Agreed that having a default value is the best option for the reason you indicated, it does not make much sense having to specify it when there is only one price.

@alex-jansen
Copy link
Contributor Author

alex-jansen commented Sep 23, 2020

After observing the usage and description of the existing /priceType property it appears it has the same role as the earlier proposed priceCategory, i.e., representing complete price specifications. We should therefore consider (instead of the original proposal) including /priceType also on /CompoundPriceSpecification and adding a new /PriceTypeEnumeration enumeration (instead of the earlier proposed /PriceCategoryEnumeration). The examples would then look as follows (building on the same changes proposed in #2869 as well):

Example 1

{
  "@context": "https://schema.org/",
  "@type": "Product",
  "sku": "coffeemaker-12345",
  "image": "https://www.example.com/coffeemaker.jpg",
  "name": "Coffee maker",
  "description": "Fast coffee maker, large",
  "gtin14": "12345678905678",
  "brand": {
    "@type": "Thing",
    "name": "SomeCoffeeBrand"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://www.example.com/coffeemaker",
    "itemCondition": "https://schema.org/NewCondition",
    "availability": "https://schema.org/InStock",
    "priceCurrency": "EUR"
    "priceSpecification": [
      {
          "@type": "UnitPriceSpecification",
          "priceType": "https://schema.org/MSRP",
          "price": 150.00
      },
      {
          "@type": "UnitPriceSpecification",
          "priceType": "https://schema.org/ListPrice",
          "price": 119.95
      },
      {
          "@type": "UnitPriceSpecification",
          "priceType": "https://schema.org/SalePrice",
          "price": 99.95
          "validFrom": "2020-10-01",
          "validThrough": "2020-10-14"
      }
      ]
    }
  }
}

Example 2

{
  "@context": "https://schema.org/",
  "@type": "Product",
  "sku": "GZDU23L/B",
  "image": "https://www.example.com/phone_5_32_slvr.jpg",
  "name": "Phone 5 inch 32GB Silver",
  "description": "New phone 5 inch screen in silver with 32GB + contract",
  "gtin14": "00821793049157",
  "mpn": "G-2PW4100-021-B",
  "brand": {
    "@type": "Thing",
    "name": "PhoneBrand"
  },
  "color": "Silver",
  "offers": {
    "@type": "Offer",
    "url": "http://www.example.com/GZDU23LB",
    "itemCondition": "https://schema.org/NewCondition",
    "availability": "https://schema.org/InStock",
    "priceCurrency": "GBP",
    "priceSpecification": [
      {
        "@type": "CompoundPriceSpecification",
        "priceComponent": [
          {
            "@type": "UnitPriceSpecification",
            "priceComponentType": "https://schema.org/Downpayment",
            "price": 215
          },
          {
            "@type": "UnitPriceSpecification",
            "priceComponentType": "https://schema.org/Installment",
            "price": 38.65,
            "billingIncrement": 1,
            "unitCode": "MON",
            "eligibleQuantity": {
              "@type": "QuantitativeValue",
              "value": 12,
              "unitCode": "MON"
            }
          },
          {
            "@type": "UnitPriceSpecification",
            "priceComponentType": "https://schema.org/Subscription",
            "price": 15,
            "billingIncrement": 1,
            "unitCode": "MON",
            "eligibleQuantity": {
              "@type": "QuantitativeValue",
              "minValue": 24,
              "unitCode": "MON"
            }
          }
        ]
      },
      {
        "@type": "CompoundPriceSpecification",
        "priceType": "https://schema.org/SalePrice",
        "validFrom": "2020-10-01",
        "validThrough": "2020-10-14",
        "priceComponent": [
          {
            "@type": "UnitPriceSpecification",
            "priceComponentType": "https://schema.org/Downpayment",
            "price": 0
          },
          {
            "@type": "UnitPriceSpecification",
            "priceComponentType": "https://schema.org/Installment",
            "price": 29.99,
            "billingIncrement": 1,
            "unitCode": "MON",
            "eligibleQuantity": {
              "@type": "QuantitativeValue",
              "value": 12,
              "unitCode": "MON"
            }
          },
          {
            "@type": "UnitPriceSpecification",
            "priceComponentType": "https://schema.org/Subscription",
            "price": 15,
            "billingIncrement": 1,
            "unitCode": "MON",
            "eligibleQuantity": {
              "@type": "QuantitativeValue",
              "minValue": 24,
              "unitCode": "MON"
            }
          }
        ]
      }
    ]
  }
}

@alex-jansen alex-jansen linked a pull request Sep 23, 2020 that will close this issue
danbri pushed a commit that referenced this issue Oct 6, 2020
* first cut at #2689

* Revert "first cut at #2712"

This reverts commit d11143e.

* first cut at #2712 (now for real)

* Add support for structured priceType values (712)

* Fix syntax error

* move new rdfs:comment for priceType

* fix another syntax error
@mfhepp
Copy link
Contributor

mfhepp commented Oct 27, 2020

+1 I am fine with this

@jsmoriss
Copy link

jsmoriss commented Sep 26, 2022

Google recently added Merchant listings results to their Rich Results Test tool, and it appears to be throwing an incorrect error: Invalid value in field "priceSpecification"

For the following markup (from https://wpsso.com/extend/plugins/wpsso/), which seems to be related to the new priceType property.

 
                   "priceSpecification": {
                        "@context": "https://schema.org",
                        "@type": "UnitPriceSpecification",
                        "price": "69.00",
                        "priceCurrency": "USD",
                        "priceType": "https://schema.org/ListPrice",
                        "eligibleQuantity": {
                            "@context": "https://schema.org",
                            "@type": "QuantitativeValue",
                            "value": "1",
                            "minValue": "1",
                            "maxValue": "1",
                            "unitText": "WordPress Site Address URL Licenses"
                        }
                    },

The Schema validator and the Google search validator are both fine with this markup - is this a bug in the Google Merchant listings validator?

js.

@alex-jansen
Copy link
Contributor Author

HI JS, since this is a Google-specific question (and not about Schema.org in general) it might be best to ask this on the Google Search forum (https://support.google.com/websearch/thread/new)

@aimeos
Copy link

aimeos commented Oct 7, 2022

Google seems to only accept this ("itemtype" attribute instead of "content") when using metatags:

<meta itemprop="priceType" itemtype="http://schema.org/SalePrice">

@Juan-Videla
Copy link

Estimados, me disculpan por molestar pero he tratado de solucionar un problema con mucha gente y aun no tengo una respuesta confiable de como se debe solucionar, resulta que en nuestro sitio tenemos 2 precios, un precio de lista y otro precio de oferta, y no se si la forma en que se han estructurado los datos es la correcta, será que me pueden ayudar.

Por ejemplo, tenemos este producto

https://www.beststore.cl/perfumes/68083-perfume-benetton-cold-edt-100ml.html

En schema, el precio normal, lo tenemos de esta forma
image

y el precio oferta esta de esta forma

image

Tengo la sensación de que esto no esta bien hecho, será que me pueden ayudar, por favor.

@jvandriel
Copy link

jvandriel commented Nov 2, 2023

First off, could you next time please ask your question in English?

Because I had to translate your message I am not quite sure what you mean with "precio de oferta". So I guessed you meant 'Sale price'. If not, have a look at: https://schema.org/PriceTypeEnumeration for an overview of the available enumerations.

As for the markup, you are probably looking to express something like this:

"offers": {
  "@type": "Offer",
  "availability": "https://schema.org/InStock",
  "priceCurrency": "CLP",
  "priceValidUntil": "2024-12-31",
  "url": "https://www.beststore.cl/perfumes/68083-perfume-benetton-cold-edt-100ml.html",
  "seller": {
    "@id": "#seller",
    "@type": "Organization",
    "name": "Best Store"
  },
  "priceSpecification": [{
    "@type": "UnitPriceSpecification",
    "priceType": "https://schema.org/SalePrice",
    "price": 8277
  }, {
    "@type": "UnitPriceSpecification",
    "priceType": "https://schema.org/ListPrice",
    "price": 8691
  }]
}

@Juan-Videla
Copy link

I apologize for the language, indeed "Precio de Oferta" is SalePrice, continuing with my example, should the salePrice be the cheapest or not? Therefore, it should be like this

"offers": {
"@type": "Offer",
"availability": "https://schema.org/InStock",
"priceCurrency": "CLP",
"priceValidUntil": "2024-12-31",
"url": "https://www.beststore.cl/perfumes/68083-perfume-benetton-cold-edt-100ml.html",
"seller": {
"@id": "#seller"
"@type": "Organization",
"name": "Best Store"
},
"priceSpecification": [{
"@type": "UnitPriceSpecification",
"priceType": "https://schema.org/SalePrice",
"price": 8277,
}, {
"@type": "UnitPriceSpecification",
"priceType": "https://schema.org/ListPrice",
"price": 8691
}]
}

@jvandriel
Copy link

Generally speaking a SalePrice is a temporary price (usually the lower price), whereas ListPrice is the regular price. Though make sure to double check with the people you are writing the markup for. It's best not to make mistakes the prices. ;-)

@Juan-Videla
Copy link

We have done it, but Google sends us the following error in its structured data

image

@jvandriel
Copy link

Your markup is not yet how Google wants it. This should work though:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Some Product",
  "offers": {
    "@type": "Offer",
    "availability": "https://schema.org/InStock",
    "url": "https://www.beststore.cl/perfumes/68083-perfume-benetton-cold-edt-100ml.html",
    "seller": {
      "@id": "#seller",
      "@type": "Organization",
      "name": "Best Store"
    },
    "priceSpecification": [{
      "@type": "UnitPriceSpecification",
      "priceType": "https://schema.org/SalePrice",
      "priceCurrency": "CLP",
      "price": 8277,
      "validThrough": "2023-12-31"
    }, {
      "@type": "UnitPriceSpecification",
      "priceType": "https://schema.org/ListPrice",
      "priceCurrency": "CLP",
      "price": 8691
    }]
  }
}

@Juan-Videla
Copy link

Jharno, thank you very much, but google shows us the following.

image

@jvandriel
Copy link

That's because you still left a <meta itemprop="priceCurrency" content="CLP"> under the Offer. Only place these under the UnitPriceSpecification. Take out the 1 priceCurrency and it will be without errors.

@Juan-Videla
Copy link

Jarno, I wanted to thank you for the help you provided, after a long time, we managed to solve the problem, thank you very much.

@Juan-Videla
Copy link

Jarno, I'm back, can you help me resolve a question, do you know how I should structure the data to be able to have the price called "before" that I show in the following image?

image

@Juan-Videla
Copy link

Estimados, tengan todos muy buenas tardes, les escribo pues desde esa tema me ayudaron hace algunos meses y ahora tengo otro problema y no se a quien recurrir, resulta que desde parte de mi equipo me dicen que tenemos cerca de 10000 errores en los datos estructurados de unos productos, les dejo un print

image

Pero no se como poder solucionarlo, al revisar schema veo la advertencia de este producto (
https://www.beststore.cl/hubs-switches/60815-cisco-business-350-series-350-24t-4g-conmutador-l3-gestionado-24-x-10-100-1000-4-x-sfp-montaje-en-rack-0889728294027.html)

image

Me gustaría saber como poder solucionar esto, si alguno de ustedes sabe me sería de mucha ayuda

@Hannah-K-Rohde
Copy link

Hi everyone,
thank you all for working on schema.org. :)
I have a question about pricing.
We have in Germany often the case, that there is a possibility of a cashback from a producer.
So for example a product costs 4000 Euro, if you buy it from a shop, you have to pay 4000 Euro,
but you can get 400 Euro back, by proving your purchase.
How could I add this information in my Markup?
I feel like I cannot use the SalePrice as it is not a direct discount because the buyer has to do something to get the money back.
Thanks so much in advance

@Hannah-K-Rohde
Copy link

Adding another point to this: What about Price/Offer combinations.
Sometimes you get the offer:
"If you buy this product A which costs XY, then you get another product B for free" or sometimes not for free but with a discounted price.
Is there a way of adding this information?

I thought about using includesObject as part of the offer for the free product
Or the addOn property as part of the offer

I'd appreciate your reply and thoughts :)

@mfhepp
Copy link
Contributor

mfhepp commented Jun 3, 2024

Bundles are supported in schema.org since 2012 - the basic pattern is

Offer -> includesObject -> TypeAndQuantityNode

There could be more examples (PRs always welcome), but the principle has been stable for a decade.

The Hotels documentation has an example, see e.g. https://schema.org/docs/hotels.html#meals.

If an offer has multiple price components, see CompoundPriceSpecification.

If an offer has an initial set of items included (i.e., the price is for the initially included item or items), and you can extend it by adding items or a bundle of items (i.e., you cannot buy these without accepting the base offer), then use addOn.

Some more references:

  1. http://wiki.goodrelations-vocabulary.org/Documentation/Bundles
  2. https://www.heppnetz.de/projects/goodrelations/primer/#3.10_Step_8:_Bundles

@steveio
Copy link

steveio commented Jun 17, 2024

Suggest extending definition of price to add range "priceFrom" priceTo" ?

@amitjain425
Copy link

need suggestion on where to put the discount details under the 'Offer' ?

@Hannah-K-Rohde
Copy link

Hannah-K-Rohde commented Feb 3, 2025

We added for a product a markup using the different priceTypes:

"@type": "Offer",
"url": "example URL ",
"itemCondition": "https://schema.org/NewCondition",
"availability": "https://schema.org/InStock",
"priceCurrency": "EUR"
"priceSpecification": [
{
"@type": "UnitPriceSpecification",
"priceType": "https://schema.org/MSRP",
"price": 2799.00
},
{
"@type": "UnitPriceSpecification",
"priceType": "https://schema.org/ListPrice",
"price": 2489.00
}
]
}

And the merchant listing is not validating anymore due to the use of multiple priceSpecifications:
"Duplicate field: priceSpecification"

Image

Could you please help me solve this? Thanks so much in advance.

@jsmoriss
Copy link

jsmoriss commented Feb 3, 2025

And the merchant listing is not validating anymore due to the use of multiple priceSpecifications: "Duplicate field: priceSpecification"

The Google Merchant validator made a change around January 10-11th that requires the "price" and "priceCurrency" properties, even though a "priceSpecification" is included. I would suggest adding the current price (ie. the list price) in the "price" property, keeping your "priceSpecification" array as-is, and checking to see if that works - in my experience, it should.

js.

@Hannah-K-Rohde
Copy link

That worked, thank you @jsmoriss 👍

"offers": {
"@type": "Offer",
"url": "...",
"price": "2279.99",
"priceCurrency": "EUR",
"priceSpecification": [
{
"@type": "UnitPriceSpecification",
"priceCategory": "https://schema.org/MSRP",
"price": "2799",
"priceCurrency": "EUR"
},
{
"@type": "UnitPriceSpecification",
"priceCategory": "https://schema.org/ListPrice",
"price": "2279.99",
"priceCurrency": "EUR"
}
],

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants