> ## Documentation Index
> Fetch the complete documentation index at: https://docs.supercycle.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rental bundles

> Group component products into one rentable bundle and build the storefront flow on the Storefront API

<Info>
  Bundles are available on request. Contact support from the app to have them turned on for your store.
</Info>

A bundle is a parent product made up of component products that customers rent together, such as a suit made of a jacket, trousers, and a waistcoat. Each component keeps its own methods, options, and inventory. Supercycle records the grouping on the parent product, and your theme builds the storefront flow on the [Storefront API](/api-reference/storefront/introduction), because bundles don't render in the Methods app block.

***

## How it works

When a customer rents a bundle:

* Each component is added to the cart as its own line item with the attributes Supercycle needs.
* Availability is checked across every component for the selected dates.
* The customer picks an option, such as a rental period, for each component.

***

## Set up a bundle

<Steps>
  <Step title="Open Bundles">
    On <Icon icon="shopify" iconType="solid" /> **[Products](https://admin.shopify.com/apps/supercycle/products)**, select **Bundles**.
  </Step>

  <Step title="Create the bundle">
    Select **Create bundle** and enter a name.
  </Step>

  <Step title="Add component products">
    Add each product that makes up the bundle. Turn on the methods each component needs before you add it, because the methods on each component decide which options are available when you create intents at checkout.
  </Step>

  <Step title="Save the bundle">
    Select **Save**. Supercycle tags the parent product `Supercycle bundle product`, tags each component `Bundle component: <parent-product-handle>`, and writes the `supercycle.bundle` metafield on the parent.
  </Step>
</Steps>

***

## Bundle metafields

The parent product carries the tag `Supercycle bundle product` and a `supercycle.bundle` metafield listing its components:

```json supercycle.bundle theme={null}
{
  "components": [
    {
      "quantity": 1,
      "product": {
        "shopifyId": 10149040324891,
        "handle": "slim-fit-suit-jacket",
        "title": "Slim Fit Suit Jacket"
      }
    },
    {
      "quantity": 1,
      "product": {
        "shopifyId": 10149040324892,
        "handle": "slim-fit-suit-trousers",
        "title": "Slim Fit Suit Trousers"
      }
    },
    {
      "quantity": 1,
      "product": {
        "shopifyId": 10149040324893,
        "handle": "slim-fit-suit-waistcoat",
        "title": "Slim Fit Suit Waistcoat"
      }
    }
  ]
}
```

Component products carry the tag `Bundle component: <parent-product-handle>`. You can reach each component's full Shopify product object in Liquid:

```liquid Look up a component theme={null}
{{ all_products['<product-handle>'] }}
```

Each component has a configuration metafield for every method turned on for it:

| Method       | Configuration metafield                                    |
| ------------ | ---------------------------------------------------------- |
| Calendar     | `product.metafields.supercycle.calendar_configuration`     |
| Membership   | `product.metafields.supercycle.membership_configuration`   |
| Subscription | `product.metafields.supercycle.subscription_configuration` |
| Resale       | `product.metafields.supercycle.resale_configuration`       |

Iterate over the components to read each one's configuration:

```liquid Read each component's calendar configuration theme={null}
{% for component in product.metafields.supercycle.bundle.components %}
  {% assign component_product = all_products[component.product.handle] %}
  {{ component_product.metafields.supercycle.calendar_configuration }}
{% endfor %}
```

Each configuration metafield holds an options array with a `global_id` per option. For example, a calendar configuration:

```json calendar_configuration theme={null}
{
  "rental_periods": [
    {
      "global_id": "gid://supercycle/CalendarRental::RentalPeriod/1",
      "name": "3 days"
    },
    {
      "global_id": "gid://supercycle/CalendarRental::RentalPeriod/2",
      "name": "4 days"
    }
  ],
  "fixed_fees": []
}
```

See [Metafields](/developers/metafields) for the full schema of each configuration metafield.

***

## Build the storefront flow

Each component goes into the cart as its own line item with the right Supercycle attributes. The steps below check availability, create an intent per component, and add every component to the cart in one request.

<Steps>
  <Step title="Check availability">
    Use the [Product availability](/api-reference/storefront/product-availability) endpoint to confirm every component is available for the selected dates. Collect the component IDs from the bundle metafield in Liquid, then post them:

    ```liquid Collect component IDs theme={null}
    {% assign component_productIds = product.metafields.supercycle.bundle.components | map: "product.shopifyId" | join: "," %}
    ```

    ```javascript Check availability theme={null}
    const component_productIds = [{{ component_productIds }}];

    const availability = await fetch("/apps/supercycle/product_availability_checks", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        productIds: component_productIds,
        rentalStart: "2025-01-01",
      }),
    }).then((res) => res.json());
    ```
  </Step>

  <Step title="Create an intent for each component">
    Each component needs an intent from the [Intent](/api-reference/storefront/intent) endpoint. The intent returns an `attributes` object with everything Supercycle needs to process the line item as a cycle, including `_cycle`, `_validations`, and `selling_plan`.

    Build a UI that lets the customer pick an option for each component, for example a dropdown of rental periods per item. Each option has a `global_id` in the component's configuration metafield, which is what you pass to the endpoint:

    ```javascript Create an intent theme={null}
    async function createIntent(variantId, optionGlobalId, rentalStart) {
      return fetch("/apps/supercycle/intents", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          variantId,
          option: {
            globalId: optionGlobalId,
            params: { rentalStart },
          },
        }),
      }).then((res) => res.json());
    }
    ```

    Call it for every component before adding to the cart:

    ```javascript Call it per component theme={null}
    const intent = await createIntent(
      variantId,
      selectedOptionGlobalId,
      "2025-01-01",
    );
    // intent.attributes contains selling_plan, _cycle, and _validations
    ```
  </Step>

  <Step title="Add the components to the cart">
    With an intent for each component, add every variant to the cart in a single request using Shopify's [multiple items cart API](https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-add-js). Set every key of `intent.attributes` directly, because each key is already the form field name Shopify expects.

    <CodeGroup>
      ```javascript AJAX example theme={null}
      const components = [
        {
          variantId: 12345678901,
          optionGlobalId: "gid://supercycle/...",
          quantity: 1,
        },
        {
          variantId: 12345678902,
          optionGlobalId: "gid://supercycle/...",
          quantity: 1,
        },
        {
          variantId: 12345678903,
          optionGlobalId: "gid://supercycle/...",
          quantity: 1,
        },
      ];

      const rentalStart = "2025-01-01";
      const formData = new FormData();

      await Promise.all(
        components.map(async ({ variantId, optionGlobalId, quantity }, index) => {
          const intent = await createIntent(variantId, optionGlobalId, rentalStart);

          formData.set(`items[${index}][id]`, variantId);
          formData.set(`items[${index}][quantity]`, quantity);

          Object.entries(intent.attributes).forEach(([key, value]) => {
            formData.set(`items[${index}][${key}]`, value);
          });
        }),
      );

      await fetch("/cart/add.js", { method: "POST", body: formData });
      ```

      ```html Form example theme={null}
      <form id="bundle-form" action="/cart/add" method="post">
        <button type="submit">Add bundle to cart</button>
      </form>

      <script>
        const form = document.getElementById("bundle-form");

        form.addEventListener("submit", async (e) => {
          e.preventDefault();

          const components = [
            {
              variantId: 12345678901,
              optionGlobalId: "gid://supercycle/...",
              quantity: 1,
            },
            {
              variantId: 12345678902,
              optionGlobalId: "gid://supercycle/...",
              quantity: 1,
            },
            {
              variantId: 12345678903,
              optionGlobalId: "gid://supercycle/...",
              quantity: 1,
            },
          ];

          const rentalStart = "2025-01-01";

          await Promise.all(
            components.map(async ({ variantId, optionGlobalId, quantity }, index) => {
              const intent = await createIntent(
                variantId,
                optionGlobalId,
                rentalStart,
              );

              const fields = {
                [`items[${index}][id]`]: variantId,
                [`items[${index}][quantity]`]: quantity,
              };

              Object.entries(intent.attributes).forEach(([key, value]) => {
                fields[`items[${index}][${key}]`] = value;
              });

              Object.entries(fields).forEach(([name, value]) => {
                const input = document.createElement("input");
                input.type = "hidden";
                input.name = name;
                input.value = value;
                form.appendChild(input);
              });
            }),
          );

          form.submit();
        });
      </script>
      ```
    </CodeGroup>
  </Step>
</Steps>
