Shopify supports product bundles — grouping multiple products or variants into a single sellable unit. This page helps developers choose the right bundle architecture for a Hydrogen storefront and implement it end to end.

Bundle Types at a Glance

AspectProduct FixedVariant FixedCustomized
Use caseStandard bundles with shared options (e.g., scent + size)Combined listing components, multi-packs, explicit variant-to-component mappingMix-and-match where customers choose components
Component limit3030150
Option limit333
InventoryAutomatic oversell protection (Shopify-managed)Automatic oversell protection (Shopify-managed)App-managed on storefront; post-add-to-cart checking built in
PricingParent-derived, allocated to components via weighted algorithmParent-derived, allocated to components via weighted algorithmExpand mode: parent-derived; Merge mode: component-derived with price adjustment in function
Storefront renderingWorks without Liquid changesWorks without Liquid changesRequires theme app blocks or custom picker UI
Custom storefront publishingSupported (requires enablement)Not supportedRequires app-managed UI
ComplexityLow — API mutations onlyLow — two-step variant associationHigh — requires a Cart Transform Shopify Function (Rust or JS/TS)

When to Use Each

Product Fixed Bundles — best default choice. Use when the merchant defines bundles with shared options (e.g., a “Hair and Skin Bundle” where the customer picks Scent and Shampoo Size). Components are configured at the product level and Shopify auto-maps variant combinations. Lowest implementation cost.

Variant Fixed Bundles — required when bundle components are combined-listings children, because combined listing children are separate products and product-fixed bundles would attach all children to every variant regardless of option selection. Also appropriate for multi-packs or any case where each variant maps to an explicit set of component variants. Configured at the variant level. Note: cannot be published to custom storefronts, so not usable in Hydrogen unless rendering is handled without the publishing API.

Customized Bundles — use only when the customer must choose their own components (mix-and-match). Requires building a Cart Transform Function, managing component data via metafields or line properties, and building a picker UI. Significantly more work; only justified when the product experience demands it.

Default recommendation

For most Hydrogen projects, start with Product Fixed Bundles. They require no Shopify Functions, no custom storefront UI beyond detection and display, and support custom storefront publishing. Use Variant Fixed when bundle components are combined-listings children (product-fixed won’t work — see below). Only move to Customized Bundles if the client’s product model requires customer-chosen components.

Variant-Fixed Bundles with Combined Listings

When bundle components come from combined-listings, product-fixed bundles don’t work — they attach every component product to every bundle variant, making option selection meaningless. Both variant-fixed and customized bundles handle this correctly, but variant-fixed is the simpler option (no Shopify Functions or custom picker UI required). (Source)

Example — “Lip Kit” bundle with two combined listings (Lip Products: Matte Lip, Gloss Lip; Liner Products: Red Liner, Nude Liner):

Bundle variantProduct-fixed (wrong)Variant-fixed (correct)
Matte Lip / Red LinerMatte Lip + Gloss Lip + Red Liner + Nude LinerMatte Lip + Red Liner
Matte Lip / Nude LinerMatte Lip + Gloss Lip + Red Liner + Nude LinerMatte Lip + Nude Liner
Gloss Lip / Red LinerMatte Lip + Gloss Lip + Red Liner + Nude LinerGloss Lip + Red Liner
Gloss Lip / Nude LinerMatte Lip + Gloss Lip + Red Liner + Nude LinerGloss Lip + Nude Liner

How Combined Listings Map to Bundle Options

  • Combined listing parentcomponent group (one bundle option)
  • Each child productoption value within that group
  • Children’s own variant options (e.g., Shade, Size) → secondary options on the bundle
  • Regular products can also be added as single-child component groups

When multiple groups contribute options with the same name and identical values, they merge into one dimension. Same name with different values are kept separate.

When a child lacks a variant for a secondary option value, that combination is excluded from the bundle product (invalid combination).

Bundle Pricing (Variant-Fixed)

price = sum(childVariantPrice × groupQuantity) for each component group

Each component group has a configurable quantity that affects both pricing and variant relationships. For groups with secondary options, the child variant matching the secondary selections determines the price.

Save Flow (Variant-Fixed)

Two-step mutation sequence:

  1. productSet (with synchronous: true) — creates/updates the bundle product with options and variants.
  2. productVariantRelationshipBulkUpdate — wires each bundle variant to its component variant IDs. Must run after step 1 because it references the returned variant IDs.

Both must succeed. If productSet succeeds but relationships fail, the bundle has empty variants with no components.

Storefront Detection

Bundles are detected via the requiresComponents boolean on ProductVariant. Query this through an aliased field to keep bundle logic separate from variant selection:

isBundle: selectedOrFirstAvailableVariant(
  ignoreUnknownOptions: true
  selectedOptions: { name: "", value: "" }
) {
  ...on ProductVariant {
    requiresComponents
    components(first: 100) {
      nodes {
        productVariant { ...ProductVariant }
        quantity
      }
    }
    groupedBy(first: 100) {
      nodes { id }
    }
  }
}

At runtime: const isBundle = Boolean(product.isBundle?.requiresComponents);

Storefront Components

BundleBadge

A visual label (“BUNDLE”) rendered on product images and cart line items. Typically absolute-positioned — requires a parent with position: relative.

BundledVariants

Renders the list of products contained in a bundle. Each row shows a thumbnail, product/variant title, and quantity, linking to the individual product page. Accepts variants: ProductVariantComponent[] from the Storefront API types.

Implementation Outline (Fixed Bundles)

These are the typical files touched when adding fixed bundle support to a Hydrogen storefront, based on Shopify’s skeleton template. Adapt paths to your project structure.

AreaFilesChanges
GraphQL fragmentsapp/lib/fragments.tsAdd requiresComponents, components, groupedBy to cart fragments
Product pageapp/routes/products.$handle.tsxQuery isBundle, render BundledVariants, pass isBundle to ProductImage and ProductForm
Collection pageapp/routes/collections.$handle.tsxQuery isBundle in PRODUCT_ITEM_FRAGMENT
Cartapp/components/CartLineItem.tsxShow BundleBadge for bundle line items
Product formapp/components/ProductForm.tsxConditional button text: “Add bundle to cart” vs “Add to cart”
Product imageapp/components/ProductImage.tsxShow BundleBadge overlay when isBundle
Product cardapp/components/ProductItem.tsxShow BundleBadge on product cards in listings
Stylesapp/styles/app.cssAdd position: relative to .product-image

Steps are not strictly ordered; dependencies between them (e.g., fragments must exist before components can query them) determine execution order. Never edit generated .d.ts files directly — run npm run codegen to regenerate after GraphQL changes.

Key Patterns

  1. requiresComponents as detection flag — single boolean on ProductVariant that identifies bundles across storefront pages and cart.
  2. Aliased query field (isBundle) — keeps bundle logic isolated from primary variant selection.
  3. components connection — provides ProductVariantComponent nodes with variant data and quantity for rendering bundled items.
  4. Badge overlayBundleBadge uses absolute positioning; parent containers must set position: relative.
  5. Conditional CTA textProductForm adjusts button copy based on isBundle prop.

Customized Bundles: Additional Requirements

Customized bundles require a Cart Transform Function (cart_transform Shopify Function, API 2025-07+) with two operations:

  • Expand — transforms a bundle line item into its component lines at checkout.
  • Merge (linesMerge) — when all components of a bundle are present in the cart, combines them into the parent variant.

Activated via cartTransformCreate mutation. Requires write_cart_transforms + write_products access scopes. Implemented in Rust or JS/TS via Shopify CLI (shopify app generate extension --template cart_transform).

Component Data Sources

ApproachSecurityUse caseNotes
MetafieldsSecure (server)Merchant-definedRecommended; requires metafield definitions
Line propertiesBrowser-editableMix-and-matchRequires validation in function

Metafields are defined under Settings > Custom data > Variants. Key metafields:

MetafieldTypePurpose
component_referenceProduct Variant — ListComponent variant IDs in the bundle
component_quantitiesInteger — List (min 1)Quantity per component (order matches refs)
component_parentsJSONReverse-lookup: parent bundles a child belongs to

Shared Behavior (All Types)

  • Discounts: calculated on the parent, allocated to components
  • Taxes: computed on components, not the parent
  • Cart display: grouped in cart/checkout; queryable via cart.line.components
  • Fulfillment: no constraint — components can be fulfilled independently
  • Refunds/returns: component-level operations
  • Reporting: component-level sales reporting

Prerequisites

  • The store must meet Shopify’s eligibility requirements (BundlesFeature GraphQL object can verify programmatically).
  • Bundles can be created via the first-party Shopify Bundles app or via the Bundles API directly from a custom Shopify app with its own admin UI. The API approach gives full control over the merchant experience and bundle configuration logic.
  • At least one bundle must be created before storefront changes take effect.
  • For custom storefronts (Hydrogen), bundle publishing must be enabled — disabled by default. Only supported for product fixed bundles. The app must be a sales channel; third-party apps activate via Partner Dashboard (App setup → “Sell Bundles”).

Limitations

  • API limits — 3 options per bundle product, 2,048 variants per bundle product, 30 component variants per bundle variant.
  • Selling plans — bundles cannot be combined with subscriptions, pre-orders, or try-before-you-buy.
  • Shopify Scripts — line item Scripts don’t apply to bundle line items. Scripts sunset June 30, 2026 — migrate to Shopify Functions.
  • No nesting — a bundle can’t be a component of another bundle.
  • App-exclusive management — once an app assigns components, only that app can modify them.
  • Custom storefront publishing — only product fixed bundles; variant fixed bundles cannot be published.

Troubleshooting

IssueSolution
No bundles visible on storefrontCreate bundles in admin (via Shopify Bundles app or custom app using the Bundles API)
No badges on product pagesVerify PRODUCT_FRAGMENT includes the isBundle alias and BundledVariants is rendered
No badges in cartVerify CART_QUERY_FRAGMENT includes requiresComponents and components

See Also