Back to Blog
Revenue Systems2026-03-05· 5 min read

Cross-Sell Without Apps: Product Metafields vs Bundle Metaobjects

Most Shopify stores install a cross-sell app like Frequently Bought Together, ReConvert, or Bold Upsell and pay $30-100/month for it. These apps inject JavaScript on every page, make external API calls to their own servers, and add 200-800ms to your page load.

You can build the same functionality with zero monthly cost, zero external dependencies, and zero performance penalty using Shopify's native metafields. Here's the architecture we use.

The two approaches

There are two ways to model cross-sell relationships in Shopify without apps:

Product metafield

custom.cross_sell_products

A list of product references directly on each product. Simple. Fast. Each product declares its own cross-sell set.

✓ Recommended

Bundle metaobject

custom.product_bundle

A separate metaobject that groups products together. More flexible but adds query complexity and a join step.

△ Use for complex bundles only

Why metafields win for cross-sell

The metafield approach is better for cross-sell because the relationship is product-centric: “when someone views Product A, also show Products B, C, and D.” The data lives on the product itself, which means:

  • One query, no joins. The Storefront API returns cross-sell products in the same query as the product data. No second round-trip.
  • Merchandiser-friendly. Store staff can update cross-sell relationships in the Shopify admin without touching code. It's just a metafield on the product.
  • Scales linearly. 100 products with cross-sell = 100 metafield values. No intermediate lookup table to maintain.

Bundle metaobjects make sense when you need a named, independently-managed group like “Summer Essentials Kit” that appears on multiple product pages. But for standard “Complete the look” or “Frequently bought with” functionality, metafields are simpler and faster.

Implementation

Step 1: Create the metafield definition

In the Shopify admin, go to Settings → Custom data → Products → Add definition:

Name: Cross-sell products

Namespace and key: custom.cross_sell_products

Type: Product reference (List)

Storefront access: Enabled (read)

Step 2: Query with the Storefront API

In a Hydrogen storefront, you query cross-sell products alongside the main product data:

const PRODUCT_QUERY = `#graphql
  query Product($handle: String!) {
    product(handle: $handle) {
      id
      title
      # ... other product fields

      crossSell: metafield(
        namespace: "custom"
        key: "cross_sell_products"
      ) {
        references(first: 4) {
          nodes {
            ... on Product {
              id
              title
              handle
              priceRange {
                minVariantPrice {
                  amount
                  currencyCode
                }
              }
              featuredImage {
                url
                altText
                width
                height
              }
            }
          }
        }
      }
    }
  }
`;

Step 3: Render the component

function CrossSell({ products }: { products: Product[] }) {
  if (!products.length) return null;

  return (
    <section className="mt-12">
      <h2 className="text-lg font-medium">Complete the look</h2>
      <div className="mt-4 grid grid-cols-2 gap-4 sm:grid-cols-4">
        {products.map((product) => (
          <a key={product.id} href={`/products/${product.handle}`}>
            <img
              src={product.featuredImage.url}
              alt={product.featuredImage.altText ?? ''}
              width={400}
              height={400}
              loading="lazy"
            />
            <p className="mt-2 text-sm">{product.title}</p>
            <p className="text-sm text-muted">
              {formatPrice(product.priceRange.minVariantPrice)}
            </p>
          </a>
        ))}
      </div>
    </section>
  );
}

Performance comparison

Metric
App-based
Metafield-based
Monthly cost
$30-100
$0
External API calls
1-3 per page
0
Added JS
50-200kb
0kb (SSR)
Page load impact
+200-800ms
~0ms
Data ownership
Third party
Yours

When to use bundle metaobjects instead

Metaobjects are the right choice when:

  • You need named, curated bundles (“The Starter Kit”) that appear across multiple products
  • Bundle composition is managed by a merchandising team independently of product data
  • You need bundle-level pricing logic (discount when bought together)

For standard “you might also like,” “complete the look,” or “frequently bought with” features, product metafields are faster to implement, easier to maintain, and cost nothing.

The bigger picture

Cross-sell is one of dozens of features that Shopify stores pay app subscriptions for when native solutions exist. Size guides, announcement bars, mega menus, product filtering, review displays. All can be built as native components in a Hydrogen storefront.

Each app you remove is money saved and performance recovered. That's what revenue infrastructure looks like: building what matters into the storefront itself, not bolting it on from outside.

How much are apps costing your store?

Our audit identifies performance-draining apps and calculates the revenue impact of removing them.

Get your audit