CatalogScan

SEO Guide · 2026

E-commerce Shipping Time Structured Data for AI Shopping Agents: ShippingDetails, DeliveryLeadTime, and Handling vs. Transit

Shipping time is a top-three purchase decision factor — and AI shopping agents are now answering "when will this arrive" queries directly. Without ShippingDetails schema, agents either skip your delivery estimate entirely or pull inaccurate text from your product description. Here's the complete markup.

TL;DR Add ShippingDetails with DeliveryLeadTime to every product's Offer block. Split delivery time into handlingTime (warehouse) and transitTime (carrier) — Google uses both to calculate a specific estimated delivery date. Use unitCode: "DAY" (not "days" or "d"). Add one ShippingDetails block per destination region for geo-targeted estimates. Shopify does not output this schema by default — add it via Liquid snippet.

Why shipping time is a top purchase decision factor — and how AI agents surface it

In post-purchase survey data collected by Baymard Institute and similar researchers, delivery speed consistently ranks alongside price and product quality as a top-three purchase decision factor. A meaningful share of shoppers will pay more for faster delivery, and a comparable share will abandon a cart if delivery estimates are vague or unavailable at product selection time.

AI shopping agents have amplified this dynamic significantly. When a user asks ChatGPT "what's the best standing desk mat that arrives by the weekend" or Google AI Mode surfaces shopping results for "next-day delivery cordless drill," the AI is not guessing at delivery times — it is reading structured data from product pages. Stores that have marked up their shipping with ShippingDetails schema get included in time-sensitive queries. Stores that haven't are invisible to those queries, regardless of how fast their actual shipping is.

The mechanism works because Google's Shopping AI and other agents can compute an estimated delivery date from your structured data: they take today's date, add your handlingTime (warehouse processing), add your transitTime (carrier delivery), and account for business days. Without the schema, they cannot perform this calculation and either skip the shipping estimate or attempt to extract it from unstructured text — which is unreliable enough that agents typically ignore it.

Complete ShippingDetails JSON-LD structure

The ShippingDetails type is nested inside your product's Offer block. The minimum viable structure requires shippingRate, shippingDestination, and deliveryTime. A complete, valid example for free US shipping in 3–5 business days:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Ergonomic Standing Desk Mat",
  "offers": {
    "@type": "Offer",
    "price": "89.00",
    "priceCurrency": "USD",
    "availability": "https://schema.org/InStock",
    "shippingDetails": {
      "@type": "OfferShippingDetails",
      "shippingRate": {
        "@type": "MonetaryAmount",
        "value": "0",
        "currency": "USD"
      },
      "shippingDestination": {
        "@type": "DefinedRegion",
        "addressCountry": "US"
      },
      "deliveryTime": {
        "@type": "ShippingDeliveryTime",
        "handlingTime": {
          "@type": "QuantitativeValue",
          "minValue": 0,
          "maxValue": 1,
          "unitCode": "DAY"
        },
        "transitTime": {
          "@type": "QuantitativeValue",
          "minValue": 3,
          "maxValue": 5,
          "unitCode": "DAY"
        },
        "businessDays": {
          "@type": "OpeningHoursSpecification",
          "dayOfWeek": [
            "https://schema.org/Monday",
            "https://schema.org/Tuesday",
            "https://schema.org/Wednesday",
            "https://schema.org/Thursday",
            "https://schema.org/Friday"
          ]
        }
      }
    }
  }
}

Key details: handlingTime is the time from order placement to shipment. transitTime is the time from shipment to delivery. businessDays defines which days are counted — omitting weekends prevents inflated estimates for orders placed on Friday. The sum of handlingTime.maxValue and transitTime.maxValue gives the maximum estimated delivery time Google displays (1 + 5 = 6 business days in this example).

Which AI agents use ShippingDetails and how

PlatformUses ShippingDetails?What it displaysFallback if missing
Google Shopping / AI Mode Yes — primary signal "Free shipping" badge, "Arrives by [date]" estimate, handling + transit calculation, ships-free filter eligibility Pulls shipping text from product description or Merchant Center feed; may omit shipping estimate entirely if inconsistent
ChatGPT Shopping Partial — via Bing Shopping API Delivery time used as ranking signal for time-sensitive queries; not displayed as a badge but factored into recommendations Ignores delivery time for ranking; product may be excluded from queries with timing constraints
Perplexity Commerce Partial — crawl-based Delivery estimate surfaced in product comparison answers when clearly structured; "ships in X days" included in product summaries Attempts text extraction from shipping policy page or product description; often inaccurate or absent
Bing Shopping Yes Shipping cost and estimated delivery in product card; free shipping label May show no shipping info or pull from page text inconsistently
Google Search (organic snippets) Limited Shipping details not shown in organic snippets; affects Shopping tab only N/A — organic snippets don't show shipping

DeliveryLeadTime: handling multiple shipping speeds

Many stores offer multiple shipping tiers — standard, expedited, and overnight. Each tier should be represented as a separate shippingDetails object within the same Offer. The shippingDetails field in an Offer accepts an array of OfferShippingDetails objects, one per method:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Wireless Noise-Cancelling Headphones",
  "offers": {
    "@type": "Offer",
    "price": "199.00",
    "priceCurrency": "USD",
    "availability": "https://schema.org/InStock",
    "shippingDetails": [
      {
        "@type": "OfferShippingDetails",
        "shippingLabel": "Standard Shipping",
        "shippingRate": {
          "@type": "MonetaryAmount",
          "value": "0",
          "currency": "USD"
        },
        "shippingDestination": {
          "@type": "DefinedRegion",
          "addressCountry": "US"
        },
        "deliveryTime": {
          "@type": "ShippingDeliveryTime",
          "handlingTime": {
            "@type": "QuantitativeValue",
            "minValue": 1,
            "maxValue": 2,
            "unitCode": "DAY"
          },
          "transitTime": {
            "@type": "QuantitativeValue",
            "minValue": 4,
            "maxValue": 7,
            "unitCode": "DAY"
          },
          "businessDays": {
            "@type": "OpeningHoursSpecification",
            "dayOfWeek": [
              "https://schema.org/Monday",
              "https://schema.org/Tuesday",
              "https://schema.org/Wednesday",
              "https://schema.org/Thursday",
              "https://schema.org/Friday"
            ]
          }
        }
      },
      {
        "@type": "OfferShippingDetails",
        "shippingLabel": "Expedited Shipping",
        "shippingRate": {
          "@type": "MonetaryAmount",
          "value": "9.99",
          "currency": "USD"
        },
        "shippingDestination": {
          "@type": "DefinedRegion",
          "addressCountry": "US"
        },
        "deliveryTime": {
          "@type": "ShippingDeliveryTime",
          "handlingTime": {
            "@type": "QuantitativeValue",
            "minValue": 0,
            "maxValue": 1,
            "unitCode": "DAY"
          },
          "transitTime": {
            "@type": "QuantitativeValue",
            "minValue": 2,
            "maxValue": 3,
            "unitCode": "DAY"
          },
          "businessDays": {
            "@type": "OpeningHoursSpecification",
            "dayOfWeek": [
              "https://schema.org/Monday",
              "https://schema.org/Tuesday",
              "https://schema.org/Wednesday",
              "https://schema.org/Thursday",
              "https://schema.org/Friday"
            ]
          }
        }
      },
      {
        "@type": "OfferShippingDetails",
        "shippingLabel": "Overnight Shipping",
        "shippingRate": {
          "@type": "MonetaryAmount",
          "value": "24.99",
          "currency": "USD"
        },
        "shippingDestination": {
          "@type": "DefinedRegion",
          "addressCountry": "US"
        },
        "deliveryTime": {
          "@type": "ShippingDeliveryTime",
          "handlingTime": {
            "@type": "QuantitativeValue",
            "minValue": 0,
            "maxValue": 0,
            "unitCode": "DAY"
          },
          "transitTime": {
            "@type": "QuantitativeValue",
            "minValue": 1,
            "maxValue": 1,
            "unitCode": "DAY"
          },
          "businessDays": {
            "@type": "OpeningHoursSpecification",
            "dayOfWeek": [
              "https://schema.org/Monday",
              "https://schema.org/Tuesday",
              "https://schema.org/Wednesday",
              "https://schema.org/Thursday",
              "https://schema.org/Friday"
            ]
          }
        }
      }
    ]
  }
}

Note that overnight shipping uses handlingTime.minValue: 0, maxValue: 0 — meaning same-day fulfillment. Only use 0 if your warehouse genuinely processes and ships orders the same day they are placed (typically for orders placed before a cutoff time). Overstating speed here creates a customer expectation your operations may not meet.

Location-based shipping: multiple countries

For stores that ship internationally, each destination country or region should have its own OfferShippingDetails block with an appropriate DefinedRegion and matching deliveryTime. AI agents use this to serve geo-targeted shipping estimates to users — a shopper in Germany seeing an AI shopping result gets your German shipping estimate, not the US estimate.

/* Multi-country ShippingDetails array — nest inside your Offer block */
"shippingDetails": [
  {
    "@type": "OfferShippingDetails",
    "shippingLabel": "US Standard Shipping",
    "shippingRate": { "@type": "MonetaryAmount", "value": "0", "currency": "USD" },
    "shippingDestination": { "@type": "DefinedRegion", "addressCountry": "US" },
    "deliveryTime": {
      "@type": "ShippingDeliveryTime",
      "handlingTime": { "@type": "QuantitativeValue", "minValue": 1, "maxValue": 2, "unitCode": "DAY" },
      "transitTime": { "@type": "QuantitativeValue", "minValue": 3, "maxValue": 5, "unitCode": "DAY" }
    }
  },
  {
    "@type": "OfferShippingDetails",
    "shippingLabel": "Canada Shipping",
    "shippingRate": { "@type": "MonetaryAmount", "value": "12.00", "currency": "USD" },
    "shippingDestination": { "@type": "DefinedRegion", "addressCountry": "CA" },
    "deliveryTime": {
      "@type": "ShippingDeliveryTime",
      "handlingTime": { "@type": "QuantitativeValue", "minValue": 1, "maxValue": 2, "unitCode": "DAY" },
      "transitTime": { "@type": "QuantitativeValue", "minValue": 7, "maxValue": 10, "unitCode": "DAY" }
    }
  },
  {
    "@type": "OfferShippingDetails",
    "shippingLabel": "EU Shipping",
    "shippingRate": { "@type": "MonetaryAmount", "value": "18.00", "currency": "USD" },
    "shippingDestination": {
      "@type": "DefinedRegion",
      "addressCountry": ["DE", "FR", "NL", "BE", "AT", "ES", "IT"]
    },
    "deliveryTime": {
      "@type": "ShippingDeliveryTime",
      "handlingTime": { "@type": "QuantitativeValue", "minValue": 1, "maxValue": 2, "unitCode": "DAY" },
      "transitTime": { "@type": "QuantitativeValue", "minValue": 10, "maxValue": 14, "unitCode": "DAY" }
    }
  }
]

The addressCountry field in DefinedRegion accepts either a single ISO 3166-1 alpha-2 country code string or an array of country codes, as shown in the EU example above. This allows you to group countries that share a shipping method without duplicating the block for each country individually.

Common ShippingDetails errors

ErrorWhat it looks likeImpactFix
Wrong value type for minValue/maxValue "minValue": "3" (string) instead of "minValue": 3 (integer) Schema validation error; Google cannot parse delivery time; no shipping estimate displayed Use unquoted integers: "minValue": 3
Missing businessDays deliveryTime block has no businessDays field Google assumes 7-day week for calculations; weekend days inflate estimated delivery dates for Monday orders Add businessDays with Monday–Friday day list using schema.org day URIs
Wrong unitCode value "unitCode": "days" or "unitCode": "d" Schema validation failure; delivery time values ignored by structured data parsers Use exactly "unitCode": "DAY" — uppercase, three letters, per UN/CEFACT
Missing shippingRate when required OfferShippingDetails block with no shippingRate Google Merchant Center flags as incomplete ShippingDetails; product may be excluded from Shopping results even if delivery time is present Always include shippingRate — for free shipping, use "value": "0"
Free and paid shipping in a single block One OfferShippingDetails attempting to describe both a free tier and a paid tier Schema parsers read only one shippingRate per block; one tier is silently dropped Use a separate OfferShippingDetails object for each shipping method; the shippingDetails field is an array
Confusing transitTime and handlingTime values Setting transitTime to total order-to-door time instead of carrier-only transit Google double-counts handling + transit and shows an inflated "arrives by" date; misleads shoppers; may increase cart abandonment Split accurately: handlingTime = warehouse processing only (order received to label printed); transitTime = carrier pickup to doorstep

Handling time vs. transit time: the distinction that Google uses for delivery date calculation

Google's Shopping AI computes a specific estimated delivery date — not a range like "5–8 business days" but a calendar date like "Arrives by Thursday, June 12" — by adding handling time and transit time separately to today's date, accounting for business days. This makes the accuracy of each component independently important.

Handling time is the period from when a customer places an order to when your warehouse hands the package to the carrier. It includes order processing (payment verification, fraud check), pick and pack time, and carrier pickup scheduling. If your warehouse processes same-day for orders before 2pm and next-day for orders after 2pm, your handling time is 0–1 business days.

Transit time is the period from carrier pickup to customer delivery. It is determined by the carrier (UPS, FedEx, USPS, DHL) and the origin-destination pair, not by you. For a ground shipment from a Pennsylvania warehouse to a California customer, transit time is typically 4–5 business days with UPS Ground.

ScenariohandlingTime (min/max DAY)transitTime (min/max DAY)Total delivery window
Same-day fulfillment, UPS Ground to continental US 0 / 0 2 / 5 2–5 business days
1-day processing, USPS Priority Mail 0 / 1 1 / 3 1–4 business days
Make-to-order product, FedEx Ground 3 / 5 2 / 5 5–10 business days
Dropshipping from US supplier (Spocket), USPS First Class 1 / 3 3 / 7 4–10 business days
Overnight express, pre-2pm order cutoff 0 / 0 1 / 1 1 business day

Be conservative but accurate. Setting handlingTime.maxValue: 1 when your warehouse sometimes takes 2 days will create a situation where Google's "arrives by" estimate is off by a day — and the customer experience damage from a missed delivery promise outweighs any conversion benefit from showing a slightly faster estimate.

Shopify-specific: adding ShippingDetails via Liquid snippet

Shopify's built-in product JSON-LD (in theme.liquid and product page templates) does not include ShippingDetails by default as of 2026. None of Shopify's native themes — Dawn, Sense, Refresh — output OfferShippingDetails in their structured data. You must add it manually.

The cleanest approach is to store your shipping times in theme settings or product metafields and render them via a dedicated Liquid snippet. This allows you to update shipping estimates store-wide without editing theme code:

{% comment %}
  Shopify ShippingDetails Liquid snippet
  File: snippets/shipping-jsonld.liquid
  Usage: {% render 'shipping-jsonld', product: product %}

  Store shipping config in theme settings (Settings > Theme settings > Shipping):
    settings.shipping_handling_min  (integer, default: 1)
    settings.shipping_handling_max  (integer, default: 2)
    settings.shipping_transit_min   (integer, default: 3)
    settings.shipping_transit_max   (integer, default: 7)
    settings.shipping_free_threshold (number, e.g.: 50.00)
    settings.shipping_base_cost     (number in dollars, e.g.: 7.99)

  Or override per-product with metafields:
    product.metafields.shipping.handling_min
    product.metafields.shipping.handling_max
    product.metafields.shipping.transit_min
    product.metafields.shipping.transit_max
{% endcomment %}

{% assign h_min = product.metafields.shipping.handling_min | default: settings.shipping_handling_min | default: 1 %}
{% assign h_max = product.metafields.shipping.handling_max | default: settings.shipping_handling_max | default: 2 %}
{% assign t_min = product.metafields.shipping.transit_min  | default: settings.shipping_transit_min  | default: 3 %}
{% assign t_max = product.metafields.shipping.transit_max  | default: settings.shipping_transit_max  | default: 7 %}
{% assign v = product.selected_or_first_available_variant %}
{% assign product_price_dollars = v.price | divided_by: 100.0 %}
{% assign free_threshold = settings.shipping_free_threshold | default: 50.0 %}

{% if product_price_dollars >= free_threshold %}
  {% assign shipping_cost = "0" %}
{% else %}
  {% assign shipping_cost = settings.shipping_base_cost | default: "7.99" %}
{% endif %}

Include this snippet in your product.liquid template (or main-product.liquid in Dawn) by adding {% render 'shipping-jsonld', product: product %} before the closing </body> tag. Configure the default values in your theme settings editor so non-technical staff can update shipping estimates without editing code. Products that need custom shipping times (oversize items, made-to-order goods) can override the defaults through Shopify Admin metafield editor.

Frequently asked questions

Does structured data for shipping time affect Google Shopping rankings?

Yes. ShippingDetails structured data enables the "Free shipping" badge, the "Arrives by [date]" callout, and filter eligibility in Google Shopping AI Mode. Products with complete ShippingDetails — including handlingTime, transitTime, and shippingRate — receive enhanced displays that products without it cannot get. Google also factors complete product data into its Shopping AI confidence scores, so missing ShippingDetails can depress your overall AI Mode visibility relative to competitors who have marked up their delivery times.

What is the correct unitCode for shipping days in JSON-LD?

The correct value is "DAY" — all uppercase, exactly three characters, per the UN/CEFACT common code standard that schema.org uses for QuantitativeValue fields. Common errors include "days", "day", "d", and "Days" — all of which fail schema validation and cause Google's parser to ignore your delivery time values entirely. Verify using Google's Rich Results Test: if your unitCode is wrong, the handlingTime and transitTime fields will show no value in the parsed output.

Can I add different shipping times for different countries with structured data?

Yes. Add multiple OfferShippingDetails objects inside your Offer's shippingDetails array — one per destination region. Each block has its own shippingDestination (using DefinedRegion with addressCountry) and its own deliveryTime with accurate min/max transit ranges for that country. The addressCountry field accepts a single country code or an array of codes for countries that share the same shipping method. AI agents use this data to serve geo-targeted estimates: a user in France sees your French shipping estimate, not your US estimate.

How does ChatGPT use shipping time data from my product pages?

ChatGPT Shopping uses ShippingDetails structured data as a ranking signal for time-sensitive purchase queries — for example, "fastest delivery cordless drill" or "coffee maker arriving before the weekend." Without structured delivery time data, ChatGPT either omits shipping from its recommendations or attempts to extract estimates from unstructured page text, which is unreliable. ChatGPT doesn't display structured shipping callouts the way Google Shopping does, but stores with well-marked fast shipping have a measurable advantage in queries that include urgency or timing constraints. The structured data gets pulled in via Bing Shopping APIs and direct crawl indexing.

Check your shipping structured data

CatalogScan detects missing ShippingDetails, wrong unitCode values, handling/transit time splits, and 15 other AI-agent-critical structured data issues across your entire catalog in 2 minutes.

Scan your store free