Optimization Guide

Ecommerce Cross-Sell and Upsell Structured Data for AI Shopping Agents

AI shopping agents answering "what accessories work with this camera," "complete the look outfit," and "is there a better version of this product" need structured product relationship data — not JavaScript recommendation widgets. Shopify's default JSON-LD has zero product relationship signals, making every cross-sell and upsell opportunity invisible to AI indexes.

TL;DR Use isRelatedTo on Product for generic cross-sells and frequently-bought-together recommendations. Use isAccessoryOrSparePartFor on the accessory Product for compatibility-based cross-sells. Use isSimilarTo for upsell links to premium versions. Use hasPart for bundle products. Drive from product metafields listing related product handles, resolve to canonical URLs in Liquid.

The Product Relationship Visibility Gap

Recommendation logic is the invisible revenue driver in most Shopify stores. "Frequently bought together," "complete the look," "you may also like," and "compatible accessories" widgets are among the highest-converting elements on any product page. These recommendations are built by apps (Bold Upsell, ReConvert, Cross Sell, native Shopify Online Store 2.0 sections) that render their output as dynamic JavaScript — invisible to AI shopping agent crawlers.

When ChatGPT Shopping answers "I need a camera bag for my Sony A7IV," it is not running your Shopify app's recommendation algorithm. It is looking for an isAccessoryOrSparePartFor relationship linking a camera bag Product to the Sony A7IV Product in structured data. Without that signal, your compatible accessories are excluded from AI agent purchase-path recommendations — even if your store's widget would have surfaced the right product for every human visitor.

Product relationship query types requiring structured data

Query type Example Required signal AI shopping behavior
Compatible accessories "what bag fits a Sony A7IV" isAccessoryOrSparePartFor on accessory → main product Returns accessories whose structured data declares compatibility with the queried product
Frequently bought together "what do people buy with this product" isRelatedTo + relationshipType: frequently-bought-together Surfaces co-purchase recommendations from structured data rather than behavioral algorithms
Premium upgrade "is there a better version of this laptop stand" isSimilarTo from base → premium product Identifies and recommends premium variants in the same product family
Complete the look "what shoes go with this dress" isRelatedTo + relationshipType: complete-the-look Builds outfit recommendation from structured relationships between apparel products
Spare parts / replacements "replacement filter for Dyson V15" isAccessoryOrSparePartFor on part → main product Routes spare parts queries directly to compatible replacement components

Product relationship property reference

Property Direction Use case Example
isRelatedTo Product → related Product(s) Cross-sells, frequently-bought-together, complete-the-look Dress → Matching Shoes
isAccessoryOrSparePartFor Accessory/part → main Product Compatible accessories, spare parts, replacement components Camera Bag → Sony A7IV
isSimilarTo Base Product → premium Product Upsells, premium versions, advanced tier recommendations Basic Plan → Pro Plan; Standard Model → Pro Model
hasPart Bundle Product → component Product(s) Bundle products, kits, starter packs Skincare Bundle → Cleanser + Toner + Moisturizer
isVariantOf Variant Product → ProductGroup Variant relationship to parent — already covered by ProductGroup/hasVariant Blue Dress (M) → Dress ProductGroup

Cross-Sell and Upsell JSON-LD Patterns

Compatible accessories with isAccessoryOrSparePartFor

The most structurally important relationship for AI agents is isAccessoryOrSparePartFor. Declare this on the accessory Product pointing to the main product's canonical URL. This is the signal that powers "what accessories work with X" queries:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Leather Camera Bag — Sony Alpha Series",
  "description": "Custom-fit leather bag for Sony Alpha A7 series mirrorless cameras. Fits A7IV, A7C, A7RV with standard lens attached.",
  "isAccessoryOrSparePartFor": [
    {
      "@type": "Product",
      "name": "Sony Alpha A7IV",
      "url": "https://catalogscan.com/products/sony-a7iv"
    },
    {
      "@type": "Product",
      "name": "Sony Alpha A7C",
      "url": "https://catalogscan.com/products/sony-a7c"
    }
  ],
  "offers": {
    "@type": "Offer",
    "price": "89.00",
    "priceCurrency": "USD",
    "availability": "https://schema.org/InStock"
  }
}

Frequently bought together with isRelatedTo

For products that are commonly purchased together but are not accessories (complementary use cases, same-occasion products), use isRelatedTo with a relationshipType additionalProperty to disambiguate from other relationship types:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "French Press Coffee Maker — 34oz",
  "isRelatedTo": [
    {
      "@type": "Product",
      "name": "Coarse Ground Coffee Subscription",
      "url": "https://catalogscan.com/products/coffee-subscription",
      "additionalProperty": {
        "@type": "PropertyValue",
        "propertyID": "relationshipType",
        "value": "frequently-bought-together"
      }
    },
    {
      "@type": "Product",
      "name": "Coffee Grinder — Conical Burr",
      "url": "https://catalogscan.com/products/burr-grinder",
      "additionalProperty": {
        "@type": "PropertyValue",
        "propertyID": "relationshipType",
        "value": "frequently-bought-together"
      }
    }
  ],
  "offers": {
    "@type": "Offer",
    "price": "44.00",
    "priceCurrency": "USD",
    "availability": "https://schema.org/InStock"
  }
}

Upsell link with isSimilarTo

For upsells — where you have a base product and a premium version that offers more features or higher quality — use isSimilarTo on the base product linking to the upgrade. This helps AI agents answer "is there a better version of this?" queries:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Adjustable Laptop Stand — Basic",
  "isSimilarTo": {
    "@type": "Product",
    "name": "Adjustable Laptop Stand — Pro (Aluminum, Dual Monitor)",
    "url": "https://catalogscan.com/products/laptop-stand-pro",
    "additionalProperty": {
      "@type": "PropertyValue",
      "propertyID": "upgradeDescription",
      "value": "The Pro model adds aluminum construction, dual monitor support, USB-C hub, and cable management — $40 more"
    }
  },
  "offers": {
    "@type": "Offer",
    "price": "29.00",
    "priceCurrency": "USD",
    "availability": "https://schema.org/InStock"
  }
}

Bundle product with hasPart

For kit or bundle products sold as a single Offer but containing multiple distinct components, use hasPart on the bundle Product listing each included component. This lets AI agents answer "what's included in this set?" queries accurately:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Beginner Watercolor Painting Kit",
  "hasPart": [
    {
      "@type": "Product",
      "name": "Watercolor Pan Set — 24 Colors",
      "description": "24-pan professional watercolor set included in kit"
    },
    {
      "@type": "Product",
      "name": "Watercolor Brush Set — 10 Brushes",
      "description": "10 mixed-size brushes included in kit"
    },
    {
      "@type": "Product",
      "name": "Cold Press Watercolor Paper Pad — A4",
      "description": "20-sheet cold press pad included in kit"
    }
  ],
  "offers": {
    "@type": "Offer",
    "price": "54.00",
    "priceCurrency": "USD",
    "availability": "https://schema.org/InStock"
  }
}

Shopify Liquid implementation with metafield-driven relationships

Store cross-sell and upsell product handles in product metafields, then resolve to canonical URLs in Liquid. Keep the list short — 2–3 relationships per product is sufficient:

{% comment %}Cross-sell / accessory relationships from product metafields{% endcomment %}
{% assign accessories_handles = product.metafields.relationships.accessories_for | split: ',' %}
{% assign related_handles = product.metafields.relationships.frequently_bought_with | split: ',' %}

{% if accessories_handles.size > 0 %}
"isAccessoryOrSparePartFor": [
  {% for handle in accessories_handles %}
    {% assign related_product = all_products[handle] %}
    {% if related_product.handle != blank %}
    {
      "@type": "Product",
      "name": {{ related_product.title | json }},
      "url": {{ related_product.url | prepend: shop.url | json }}
    }{% unless forloop.last %},{% endunless %}
    {% endif %}
  {% endfor %}
],
{% endif %}

{% if related_handles.size > 0 %}
"isRelatedTo": [
  {% for handle in related_handles %}
    {% assign rel_product = all_products[handle] %}
    {% if rel_product.handle != blank %}
    {
      "@type": "Product",
      "name": {{ rel_product.title | json }},
      "url": {{ rel_product.url | prepend: shop.url | json }}
    }{% unless forloop.last %},{% endunless %}
    {% endif %}
  {% endfor %}
],
{% endif %}

Common Mistakes

1. Relying on JavaScript recommendation widgets for cross-sell signals

ReConvert, Bold Upsell, LimeSpot, and similar recommendation apps inject their cross-sell widgets via JavaScript after page load. AI agent crawlers (GPTBot, PerplexityBot, ClaudeBot) do not execute JavaScript. Even if your recommendation app always shows the right accessory, that relationship is completely invisible to structured-data-reading AI agents. The JSON-LD relationships and the app widgets serve completely separate audiences.

2. Using isRelatedTo when isAccessoryOrSparePartFor is correct

For accessories and spare parts, isAccessoryOrSparePartFor is semantically stronger and more specific than isRelatedTo. AI agents weight compatibility-declared relationships more heavily for accessory queries. Using the generic isRelatedTo on a camera bag for a specific camera body reduces the confidence of the compatibility claim.

3. Pointing isAccessoryOrSparePartFor at a homepage or collection URL

The isAccessoryOrSparePartFor URL should point to the canonical product detail page (PDP) of the main product — https://yourstore.com/products/sony-a7iv, not https://yourstore.com/collections/cameras. AI agents build a product identity graph from PDP URLs. A collection URL creates an ambiguous compatibility claim against an entire category rather than a specific model.

4. Too many isRelatedTo links per product

Adding 10-15 isRelatedTo products in an attempt to cast a wide cross-sell net reduces the signal quality for each individual relationship. AI agents treat dense relationship graphs as lower-confidence recommendations. Limit to 2-3 highest-relevance cross-sells per product — the ones with the strongest genuine co-purchase or complementary-use relationship.

5. Using hasPart for "frequently bought together" instead of isRelatedTo

hasPart declares that the listed products are physical components of the bundle product — they are included in what is sold. Using hasPart for "frequently bought together" recommendations (products sold separately) creates a false claim that the recommended products are included in the current product's purchase. AI agents may generate misleading bundle claims from this signal. Use isRelatedTo for separate products that are often co-purchased.

Implementation Checklist

Frequently Asked Questions

How do I mark up cross-sell and accessory products in Shopify JSON-LD?

Use isRelatedTo on the Product for generic cross-sells and frequently-bought-together recommendations. Use isAccessoryOrSparePartFor on the accessory Product pointing to the main product's canonical PDP URL for compatibility-based accessories. Use isSimilarTo on base products linking to premium upgrade versions. Store related product handles in metafields and resolve to URLs in Liquid.

Does Shopify include product relationship signals in its default JSON-LD?

No. Shopify's default product JSON-LD generates a standalone Product with no relationships. Recommendation widgets from apps (Bold Upsell, ReConvert, Cross Sell) are rendered by JavaScript after page load — they are invisible to AI agent crawlers. You must add relationship signals manually to product.liquid.

What is the difference between isRelatedTo and isAccessoryOrSparePartFor?

isRelatedTo is a broad relationship — "these products are meaningfully connected." isAccessoryOrSparePartFor is a specific compatibility declaration — "this Product is an accessory or part that works with that Product." Use isAccessoryOrSparePartFor on the accessory item for AI agents to answer "what works with X" queries. It carries stronger compatibility semantics than a generic isRelatedTo.

How do I mark up bundle and frequently-bought-together products?

For bundle products sold as one Offer containing multiple physical components, use hasPart on the bundle Product listing each included component Product. For products that are sold separately but often co-purchased, use isRelatedTo with an additionalProperty: relationshipType: frequently-bought-together. Do not use hasPart for co-purchase recommendations — it implies the products are physically included in the bundle.

Can I mark up "complete the look" outfit recommendations in structured data?

Yes. Use isRelatedTo on the anchor product (a dress, for example) linking to recommended accessories (shoes, bag, belt). Add an additionalProperty with propertyID: relationshipType and value complete-the-look to distinguish these from generic cross-sells. For the reverse direction, add isRelatedTo on each accessory pointing back to the anchor garment as well.

Related Resources