> ## 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.

# Create rental bundles

> Group multiple products into a single rentable bundle and build a custom frontend experience using the Storefront API.

<Note>
  Bundles are available on request. [Get in touch](mailto:support@supercycle.com) and we will enable it for your store.
</Note>

## Set up bundles

<Steps>
  <Step title="Navigate to Products → Bundles">
    In your Supercycle dashboard, go to **Products** and select **Bundles**.
  </Step>

  <Step title="Create a new bundle">
    Click **Create bundle** and give it a name.
  </Step>

  <Step title="Add component products">
    Add each product that makes up the bundle. Before adding a component, ensure it has the relevant methods enabled in Supercycle. The methods configured on each component determine which options are available when creating intents at checkout.
  </Step>

  <Step title="Save the bundle">
    Save the bundle. Supercycle will tag the parent product as `Supercycle bundle product` and each component with `Bundle component: <parent-product-handle>`, and attach the `supercycle.bundle` metafield to the parent.
  </Step>
</Steps>

## Bundle metafields

Parent bundle products will have the tag `Supercycle bundle product` and a `supercycle.bundle` metafield:

```json 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 will have the tag `Bundle component: <parent-product-handle>`.

You can access each component product's full Shopify object in Liquid:

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

Each component product has a configuration metafield for every method enabled on 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`       |

You can iterate over all components and read the relevant configuration:

```liquid 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 contains an options array with a `global_id` for each option. For example, a calendar configuration:

```json 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 a frontend experience

Each component product must be added to the cart as its own line item with the correct Supercycle attributes. The steps below walk through checking availability, creating intents, and adding all components to the cart in one request.

<Steps>
  <Step title="Check availability">
    Use the [Product availability API](/api-reference/storefront/product-availability) to confirm all component products are available for the selected dates.

    Extract component IDs from the bundle metafield in Liquid:

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

    Then check availability:

    ```js 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 product needs an intent created via the [Intent API](/api-reference/storefront/intent). The intent returns an `attributes` object containing everything Supercycle needs to process the line item as a cycle, including `_cycle`, `_validations`, and `selling_plan`.

    You will need to build a UI that lets the customer select an option for each component product, 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 intent endpoint:

    ```js 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 this for every component before adding to the cart:

    ```js theme={null}
    const intent = await createIntent(
      variantId,
      selectedOptionGlobalId,
      "2025-01-01",
    );
    // intent.attributes contains selling_plan, _cycle, _validations, etc.
    ```
  </Step>

  <Step title="Add all components to the cart">
    Once you have an intent for each component, add all variants 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).

    Use `FormData` and set all `intent.attributes` keys directly. Each attribute key is already the correct form field name:

    <CodeGroup>
      ```js 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>
