> For the complete documentation index, see [llms.txt](https://docs.datajet-app.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.datajet-app.com/functions/functions/cart-transform.md).

# Cart Transform

Merge cart lines into bundles, expand bundle SKUs into components, and update line presentation with JavaScript.

A cart transform reshapes the lines of the cart itself — merge several lines into a single bundle line, expand one line into its components, or update how a line is presented. Your code inspects the cart and returns a list of operations.

```javascript
var ops = [];

var bundleLines = input.cart.lines.filter(function (line) {
  var product = line.merchandise && line.merchandise.product;
  return product && product.hasTags && product.hasTags.some(function (t) { return t.hasTag; });
});

if (bundleLines.length >= 2) {
  ops.push({
    linesMerge: {
      cartLines: bundleLines.map(function (l) { return { cartLineId: l.id, quantity: l.quantity }; }),
      parentVariantId: 'gid://shopify/ProductVariant/…',
      title: 'Bundle',
      price: { percentageDecrease: { value: 10 } },
    },
  });
}

return { operations: ops };
```

{% hint style="info" %}
A store can have **only one cart transform function** — the Functions Console lets you create a single one, in the *General* or *Tags* family. Its title is internal: buyers never see it.
{% endhint %}

### Operations

Return an array of operations, or `{ operations: [...] }`. Each operation is an object with exactly one of these keys:

| Operation | Shape                                                                                                      | Effect                                                               |
| --------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Merge     | `{ linesMerge: { cartLines: [{ cartLineId, quantity }], parentVariantId, title?, image?, price? } }`       | Combines lines into one bundle line, presented as the parent variant |
| Expand    | `{ lineExpand: { cartLineId, title?, image?, expandedCartItems: [{ merchandiseId, quantity, price? }] } }` | Splits one line into its component items                             |
| Update    | `{ lineUpdate: { cartLineId, title?, image?, price? } }`                                                   | Overrides a line's title, image or price                             |

Price shapes: merge uses `price: { percentageDecrease: { value: 10 } }`; expanded items use `price: { adjustment: { fixedPricePerUnit: { amount: '10.0' } } }`.

Returning an empty array leaves the cart unchanged. Operations of any other shape are ignored.

{% hint style="warning" %}
`lineUpdate` (price/title/image overrides) works on **Shopify Plus and development stores only** — prefer merge and expand for rules that must work everywhere.
{% endhint %}

### The input

* Cart lines are `input.cart.lines`, each with an `id`, `quantity`, `cost` and merchandise (variant `id`, `sku`, product data — the *Tags* family adds product and customer `hasTags`).
* There is **no cart-level cost** — money lives per line in `line.cost`, and `input.presentmentCurrencyRate` at the root converts shop-currency amounts to what the buyer pays.
* The variant IDs you reference (`parentVariantId`, `merchandiseId`) must be existing, published variants — supply them via [variables](/functions/functions/families-and-variables.md) so merchants can set them without code changes.

### How DataJet manages the transform

* The function runs on **every cart change**, so keep the code fast and return `{ operations: [] }` early when there's nothing to do.
* Turning the function off doesn't delete anything — the off switch is stored in the function's configuration and the code simply stops producing operations.
* Deleting the function removes it from Shopify entirely, including its configuration — the code remains available in the version history.
* Cart transforms have no page in the Shopify admin; they're managed only from the Functions Console.

### Example: expand a bundle SKU

*Family: General* — set `BUNDLE_SKU` and the component variant IDs as [code variables](/functions/functions/families-and-variables.md).

```javascript
var ops = [];
var componentIds = (vars.BUNDLE_COMPONENT_IDS || '').split(',');

input.cart.lines.forEach(function (line) {
  var variant = line.merchandise;
  if (variant && variant.sku === vars.BUNDLE_SKU && componentIds.length > 1) {
    ops.push({
      lineExpand: {
        cartLineId: line.id,
        expandedCartItems: componentIds.map(function (id) {
          return { merchandiseId: 'gid://shopify/ProductVariant/' + id.trim(), quantity: 1 };
        }),
      },
    });
  }
});

return { operations: ops };
```

### Notes

* Cart transforms don't use the **Run on** steps — they apply whenever the cart changes.
* If your code throws, the cart is left unchanged (fail open).
* See also: [Cart and Checkout Validation](/functions/functions/cart-and-checkout-validation.md), [Delivery Customization](/functions/functions/delivery-customization.md), [Payment Customization](/functions/functions/payment-customization.md), [Discounts](/functions/functions/discounts.md).
