Update 25th Feb 2026 – Since this was developed, Google has updated its documentation on implementing shipping structured data, moving most of the details to a single organisation page. This article is still valid, but you may want to implement shipping in the latest preferred way.
Google has recently created a stir with their recent structured data warnings about missing shipping information in the Merchant Listings report (“Missing field ‘shippingDetails’ (in ‘offers’)”) backed up with an email to worry everyone (“Missing shipping information in your Merchant Listings on your sites”). Currently, I suspect most CMSes do not provide a way to get hold of and create this shipping information. So I built a JavaScript template to help you make your own advanced shipping information.


Do you need it?
The presence of shipping information on a product page means you can be eligible for the shipping based rich snippets. This can help consumers get a better idea on how much your products will eventually cost, without them having to visit your site and fill in details. Giving you an edge over your competition, especially if you offer free shipping.

If you have a Merchant Centre account and have filled in your shipping information there (via feed, via settings), you do not need to also add it to your website. But just adding one shipping item will remove the warning and may increase your chances of getting the snippets.

Complex pricing with shipping providers
Some sites use APIs with shipping providers to get highly variable shipping prices based on factors like destination, size and weight. It would be practically impossible to mark up all the options, and if you did, your page would be massive. So how do you deal with this?
I suggest you mark up what you can and what will benefit you the most. Try and indicate when you offer free shipping, as that benefits you the most. Do you offer free shipping for certain destinations or product price levels?
Then you could mark up maximum costs for different destinations, product sizes, and weights.
In the future, Google may let you define well-known shipping providers so that they can work out the shipping for you. This is already possible in the Merchant Centre if the required product attributes (weight and dimensions) are included.
Our BigCommerce Shipping Information solution
Our SEO Rich Snippets app for BigCommerce lets you define shipping information for your organisation and products. If you need a more complex solution, I have created a BigCommerce script that gathers product information and includes example scenarios.
Connecting Shipping Information to your Product Offer markup
It’s important that all your structured data is correctly linked together. This solution assumes you already have Product Structured Data on your product pages. It’s not going to try and replace it. It is going to link shipping details with it.
Shipping details structured data is part of the product’s Offer. So we need to merge it into your existing Offer on the page. This is done by indicating that our shipping details is part of the same offer. To do that, it needs to use the same “@id”. In the following example, the shippingDetails will be added to the products Offer because the two separate items use the same “@id”.
<!-- Existing Product schema -->
<script type="application/ld+json">
{
"@context":"https://schema.org",
"@type":"Product",
"offers": [{
"@type": "Offer",
"@id": "#offer"
"price": 100,
"priceCurrency": "USD",
...
}],
...
}
</script>
<!-- Shipping Information schema -->
<script type="application/ld+json">
{
"@context":"https://schema.org",
"@id": "#offer"
"shippingDetails": [...],
...
}
</script>
So the first thing in our script is a variable to set the value for the “@id”. The script will then merge its shipping information correctly.
var offerId = '#offer';
I’ve also provided a complete script template to help you get started with your solution…
The Shipping Information items
The next part of the code is about adding your list of shipping information items to add to the product. These items are stored in an array:
shippingItems = [];
Each item can have the following parameters.
| Parameter | Description |
|---|---|
| cost | The cost of shipping to the specified destination. One of cost or maxCost is required. A number. |
| maxCost | The maximum cost of shipping to the specified destination. One of cost or maxCost is required. A number. |
| currency | Required. The currency code of the cost. e.g. “USD”. The currency should be the same as submitted for the offer price. |
| country | Required. 2 letter country codes. You can specify more than one in an array. e.g. [“US”, “CA”] |
| region | Optional. Do not combine with postalCode. 2 or 3 letter codes. Google currently supports it in US, Australia, and Japan. You can specify more than one in an array. e.g. [“NY”, “CA”] |
| postalCode | Optional. Do not combine with region. Google currently support it in Australia, Canada, and the US. You can specify more than one in an array, and you may also be able to supply ranges and wild cards. e.g. Metropolitan Adelaide: [“5000-5117”, “512*”, “5150”, “5158”, “5159”, “516*-517*”, “5950”, “5960”]. |
| handlingTimeMinDays | Required. The typical minimum delay in days between the receipt of the order and the goods leaving the warehouse. |
| handlingTimeMaxDays | Required. The typical maximum delay in days between the receipt of the order and the goods leaving the warehouse. |
| transitTimeMinDays | Required. The typical minimum delay in days between when the order has been sent for delivery and when the goods reach the final customer. |
| transitTimeMaxDays | Required. The typical maximum delay in days between when the order has been sent for delivery and when the goods reach the final customer. |
Some examples
This example items js file contains all the examples that follow.
All shipping in a country is free
If you can do this, you will look better than your competitors. The main thing here is to specify where you offer free shipping. You don’t want to have to pay to ship to Antarctica. And remember to fill in the correct delivery times.
shippingItems.push({
cost: 0, // FREE
currency: "USD",
country: ["US"], // In the US
handlingTimeMinDays: 0,
handlingTimeMaxDays: 1,
transitTimeMinDays: 1,
transitTimeMaxDays: 3
});
Shipping in some postcodes are free
Let’s make it free around where I live (the Adelaide Metropolitan postcodes).
shippingItems.push({
cost: 0, // FREE
currency: "AUD",
country: ["AU"], // In Australia
postalCode: ["5000-5117", "512*", "5150", "5158", "5159", "516*-517*", "5950", "5960"], // Adelaide Metropolitan postcodes
handlingTimeMinDays: 0,
handlingTimeMaxDays: 1,
transitTimeMinDays: 1,
transitTimeMaxDays: 3
});
Shipping is a fixed price for the rest of my state
Let’s say, max transit time for the state can be a bit longer
shippingItems.push({
cost: 10,
currency: "AUD",
country: ["AU"],
region: ["SA"],// In South Australia
handlingTimeMinDays: 0,
handlingTimeMaxDays: 1,
transitTimeMinDays: 1,
transitTimeMaxDays: 5
});
Shipping will never exceed a price
Here we use the maxCost instead of cost. So say our shipping provider will never charge more than $15 in Australia.
shippingItems.push({
maxCost: 15,
currency: "AUD",
country: ["AU"],
handlingTimeMinDays: 0,
handlingTimeMaxDays: 1,
transitTimeMinDays: 3,
transitTimeMaxDays: 7
});
Free shipping for products over a certain price
Here we need to get hold of some product information. This is how you can do it in BigCommerce.
var bigCommerceDecimalToken = '.';
var bigCommerceThousandsToken = ','
function parseBigCommerceDimension(text) {
var newText = text.toLowerCase().replace("{{settings.measurements.length}}".toLowerCase(), "").replace("{{settings.measurements.weight}}".toLowerCase(), "").replace(bigCommerceDecimalToken, ".").replace(bigCommerceThousandsToken, "").trim();
if (newText) {
return parseFloat(newText);
}
else return -1;
}
var product = {
price: {{~#if product.price.with_tax}}{{product.price.with_tax.value}}{{else}}{{~#if product.price.without_tax}}{{product.price.without_tax.value}}{{else}}-1{{~/if}}{{~/if}}, // -1 = price unknown
priceCurrency: '{{~#if product.price.with_tax}}{{product.price.with_tax.currency}}{{else}}{{~#if product.price.without_tax}}{{product.price.without_tax.currency}}{{else}}{{~/if}}{{~/if}}',
weight: parseBigCommerceDimension('{{product.weight}}'),
width: parseBigCommerceDimension('{{product.width}}'),
height: parseBigCommerceDimension('{{product.height}}'),
depth: parseBigCommerceDimension('{{product.depth}}'),
fixedShippingPrice: {{~#if product.shipping.price}}{{product.shipping.price.value}}{{else}}-1{{~/if}}, // -1 = no fixed price. 0 = free
fixedShippingCurrency: '{{~#if product.shipping.price}}{{product.shipping.price.currency}}{{~/if}}'
};
With that product information, we can now do some fun things. Let’s do free shipping in Australia for products over $100.
if (product.price > 100) {
shippingItems.push({
cost: 0,
currency: "GBP",
country: ["GB"],
handlingTimeMinDays: 0,
handlingTimeMaxDays: 1,
transitTimeMinDays: 3,
transitTimeMaxDays: 7
});
} else {
// Costs for products under $100
}
Please share any solutions you have to get product information on other platforms.
Using a fixed shipping price
In BigCommerce, you can specify a product’s exact shipping price, including free shipping. So it makes sense for that to override other options.
if (product.fixedShippingPrice >= 0) {
shippingItems.push({
cost: product.fixedShippingPrice,
currency: product.fixedShippingCurrency,
country: ["GB"],
handlingTimeMinDays: 0,
handlingTimeMaxDays: 1,
transitTimeMinDays: 3,
transitTimeMaxDays: 7
});
} else {
// non fixed price shipping options
}
Using formulas for shipping cost
You may vary your shipping cost by other factors like weight. Maybe different costs for different weight ranges. Or even a special formula.
if (product.weight < 10) {
shippingItems.push({
cost: 5, // min shipping cost
currency: "GBP",
country: ["GB"],
handlingTimeMinDays: 0,
handlingTimeMaxDays: 1,
transitTimeMinDays: 3,
transitTimeMaxDays: 7
});
} else if (product.weight < 20) {
shippingItems.push({
cost: 10,
currency: "GBP",
country: ["GB"],
handlingTimeMinDays: 0,
handlingTimeMaxDays: 1,
transitTimeMinDays: 3,
transitTimeMaxDays: 7
});
} else if (product.weight > 100) {
shippingItems.push({
cost: 50, // the max shipping cost
currency: "GBP",
country: ["GB"],
handlingTimeMinDays: 0,
handlingTimeMaxDays: 1,
transitTimeMinDays: 3,
transitTimeMaxDays: 7
});
} else {
shippingItems.push({
cost: 10 + (product.weight / 3), // varying shipping cost
currency: "GBP",
country: ["GB"],
handlingTimeMinDays: 0,
handlingTimeMaxDays: 1,
transitTimeMinDays: 3,
transitTimeMaxDays: 7
});
}
The code to build the structured data
Once you have populated your shippingItems array, add the following code, which will build and add the required structured data to your page:
var shippingDetails = [];
for (var i = 0; i < shippingItems.length; i++) {
var item = shippingItems[i];
var shippingRate = {"@type": "MonetaryAmount"};
if (item.maxCost !== 'undefined') shippingRate.maxValue = item.maxCost;
if (item.cost !== 'undefined') shippingRate.value = item.cost;
if (item.currency) shippingRate.currency = item.currency;
var shippingDestination = {"@type": "DefinedRegion"};
if (item.country) shippingDestination.addressCountry = item.country;
if (item.region) shippingDestination.addressRegion = item.region;
if (item.postalCode) shippingDestination.postalCode = item.postalCode;
var handlingTime = {"@type": "QuantitativeValue", "unitCode": "DAY"};
if (item.handlingTimeMinDays !== 'undefined') handlingTime.minValue = item.handlingTimeMinDays;
if (item.handlingTimeMaxDays !== 'undefined') handlingTime.maxValue = item.handlingTimeMaxDays;
var transitTime = {"@type": "QuantitativeValue", "unitCode": "DAY"};
if (item.transitTimeMinDays !== 'undefined') transitTime.minValue = item.transitTimeMinDays;
if (item.transitTimeMaxDays !== 'undefined') transitTime.maxValue = item.transitTimeMaxDays;
shippingDetails.push({
"@type": "OfferShippingDetails",
"shippingRate": shippingRate,
"shippingDestination": shippingDestination,
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": handlingTime,
"transitTime": transitTime
}
});
}
var offer = {
"@context":"https://schema.org",
"@id": offerId,
"shippingDetails": shippingDetails
};
var script = document.createElement('script');
script.id = 'wsa-rich-snippets-jsonld-offer-shipping';
script.setAttribute('type', 'application/ld+json');
script.textContent = JSON.stringify(offer);
document.head.appendChild(script);
Summary
The complete template script is a great starting point to help you include your shipping info on your product pages. Remember that just adding a few shipping options, especially the free ones, can make you stand out from your competitors.
References
- Shipping and Returns information on Google Search web results
- Search Central – Product Structured Data – Shipping
- Merchant Centre – Supported structured data attributes and values – Shipping
- Merchant Centre Feed – Shipping attribute
- Merchant Centre Admin – Shipping settings
- Example Advanced Shipping Script – BigCommerce & SEO Rich Snippets
- Example Advanced Shipping Script – General