Skip to content
Auto-Add Items to Cart
Browse other ways to boost conversion rate & profit

Auto-Add Items to Cart

In this tutorial, we’ll show you how to automatically add products to the cart when customers meet offer requirements. This simple feature creates a smoother shopping experience, reduces confusion, and helps boost conversions.

Compatible Themes: This code should work on all free Shopify themes (Dawn, Refresh, Craft, Studio, Publisher, Crave, Origin, Taste, Colorblock, Sense, Ride, Spotlight).

 

Create metaobject definition and entries

Name: Conditional ATC Item

Type: conditional_atc_item

Fields:

  • Name:
    • Key: conditional_atc_item.name
    • Type: single line text
    • Required field
  • Threshold Value:
    • Key: conditional_atc_item.threshold_value
    • Type: decimal, one value
    • Minimum value: 0
    • Required field
  • Product List:
    • Key: conditional_atc_item.product_list
    • Type: list of products
    • Required field
  • Use As Exclusion:
    • Key: conditional_atc_item.use_as_exclusion
    • Type: True or false
    • Required field
  • Variant To Add:
    • Key: conditional_atc_item.variant_to_add
    • Type: one product variant
    • Required field

Edit settings_schema.json

  {
    "name": "Conditional ATC Item Settings",
    "settings": [
      {
        "type": "checkbox",
        "id": "enable_conditional_atc_item",
        "default": false,
        "label": "Enable conditional add/remove?"
      },
      {
        "type": "select",
        "id": "atc_item_condition_type",
        "label": "Condition Type",
        "options": [
          { "value": "cart_total", "label": "Cart total" },
          { "value": "cart_subtotal", "label": "Cart subtotal" },
          { "value": "cart_item_count", "label": "Cart item count" }
        ],
        "default": "cart_total"
      },
      {
        "type": "text",
        "id": "atc_item_metaobject_handle",
        "label": "Metaobject entry handle for condition settings",
        "default": "cart-gift-item"
      }
    ]
  }

Edit main-cart-items.liquid and cart-drawer.liquid

Fetch metaobject values

{% assign enable_conditional_atc_item = settings.enable_conditional_atc_item %}
{% assign atc_item_condition_type = settings.atc_item_condition_type %}
{% assign atc_item_metaobject_handle = settings.atc_item_metaobject_handle %}

{% assign condition_entry = null %}
{% if atc_item_metaobject_handle != blank %}
  {% assign condition_entry = shop.metaobjects.conditional_atc_item[atc_item_metaobject_handle] %}
{% endif %}

{% if condition_entry %}
  {% assign atc_item_product_list_ids = condition_entry.product_list.value | map: 'id' | join: ',' | default: '' %}
  {% assign atc_item_use_as_exclusion = condition_entry.use_as_exclusion %}
  {% assign atc_item_variant_to_add_ids = condition_entry.variant_to_add.value | map: 'id' | join: ',' | default: '' %}
  {% assign atc_item_threshold_value = condition_entry.threshold_value.value | default: 0 %}
{% endif %}

Add data attributes to the cart-items and cart-drawer-items tags

  data-enable-conditional-atc-item="{{ enable_conditional_atc_item }}"
  data-atc-item-condition-type="{{ atc_item_condition_type }}"
  data-atc-item-metaobject-handle="{{ atc_item_metaobject_handle }}"
  data-atc-item-product-list="{{ atc_item_product_list_ids | strip }}"
  data-atc-item-use-as-exclusion="{{ atc_item_use_as_exclusion }}"
  data-atc-item-variant-to-add="{{ atc_item_variant_to_add_ids | strip }}"
  data-atc-item-threshold-value="{{ atc_item_threshold_value }}"

Add data attribute to cart-remove-button tags

data-variant-id="{{ item.variant.id }}"

Edit cart.js

Add to variable setup to remember whether user removed the item

(function() {
  const cartItemsElement =
    document.querySelector('cart-items') || document.querySelector('cart-drawer-items');
  const atcItemFeatureIsEnabled = cartItemsElement?.dataset.enableConditionalAtcItem === 'true';

  if (atcItemFeatureIsEnabled && typeof window.userRemovedAutoAtcItem === 'undefined') {
    const stored = localStorage.getItem('userRemovedAutoAtcItemData');
    if (stored) {
      try {
        const parsed = JSON.parse(stored);
        const now = Date.now();
        if (parsed.value === true && parsed.expiresAt && now < parsed.expiresAt) {
          window.userRemovedAutoAtcItem = true;
        } else {
          window.userRemovedAutoAtcItem = false;
          localStorage.removeItem('userRemovedAutoAtcItemData');
        }
      } catch (err) {
        window.userRemovedAutoAtcItem = false;
        localStorage.removeItem('userRemovedAutoAtcItemData');
      }
    } else {
      window.userRemovedAutoAtcItem = false;
    }
  }
})();

Add to the CartRemoveButton class constructor method

      const cartItemsElem = this.closest('cart-items') || this.closest('cart-drawer-items');
      if (
        cartItemsElem &&
        cartItemsElem.dataset.enableConditionalAtcItem === 'true' &&
        this.isAtcItemVariant(cartItemsElem, this.dataset.variantId)
      ) {
        window.userRemovedAutoAtcItem = true;
        const oneDayMs = 24 * 60 * 60 * 1000;
        const expiresAt = Date.now() + oneDayMs;
        const dataToStore = { value: true, expiresAt };
        localStorage.setItem('userRemovedAutoAtcItemData', JSON.stringify(dataToStore));
      }

Add new method to the CartRemoveButton class

  isAtcItemVariant(cartItemsElem, variantId) {
    const atcItemVariantsCsv = cartItemsElem.dataset.atcItemVariantToAdd || '';
    const atcItemVariantsArray = atcItemVariantsCsv.split(',').map(s => s.trim()).filter(Boolean);
    const isAtcItem = atcItemVariantsArray.includes(String(variantId));
    return isAtcItem;
  }

Add variable definition in the CartItems class

isForcedCartUpdate = false;

Add to the CartItems class connectedCallback method

    if (this.dataset.enableConditionalAtcItem === 'true' && this.tagName === 'CART-ITEMS') {
      this.checkConditionalAtcOnInitialLoad();
    }

Add to the CartItems class updateQuantity method

    if (this.isForcedCartUpdate) {
      return;
    }
        const { enableConditionalAtcItem } = this.getConditionalAtcItemSettings();
        if (enableConditionalAtcItem) {
          this.handleConditionalAtcItem(parsedState);
        }

Add new methods to the CartItems class

  getConditionalAtcItemSettings() {
    const enableConditionalAtcItem = this.dataset.enableConditionalAtcItem === 'true';
    const atcItemConditionType = this.dataset.atcItemConditionType || '';
    const atcItemMetaobjectHandle = this.dataset.atcItemMetaobjectHandle || '';
    const atcItemProductListCsv = this.dataset.atcItemProductList || '';
    const useAsExclusion = (this.dataset.atcItemUseAsExclusion === 'true');
    const atcItemThresholdValue = parseFloat(this.dataset.atcItemThresholdValue || '0');

    const parseCsv = (csv) => {
      return csv
        .split(',')
        .map((s) => s.trim())
        .filter((s) => s.length > 0);
    };

    const atcItemVariantToAddCsv = this.dataset.atcItemVariantToAdd || '';
    const atcItemVariantToAdd = parseCsv(atcItemVariantToAddCsv);
    const atcItemProductList = parseCsv(atcItemProductListCsv);

    return {
      enableConditionalAtcItem,
      atcItemConditionType,
      atcItemMetaobjectHandle,
      atcItemProductList,
      useAsExclusion,
      atcItemVariantToAdd,
      atcItemThresholdValue
    };
  }

  checkConditionalAtcOnInitialLoad() {
    fetch(`${routes.cart_url}.js`, { ...fetchConfig('json') })
      .then((resp) => resp.json())
      .then((cartData) => {
        const pseudoParsedState = {
          items: cartData.items
        };
        this.handleConditionalAtcItem(pseudoParsedState);
      })
      .catch((err) => {});
  }

  handleConditionalAtcItem(parsedState) {
    if (this.isForcedCartUpdate) {
      return;
    }

    const {
      enableConditionalAtcItem,
      atcItemConditionType,
      atcItemProductList,
      useAsExclusion,
      atcItemVariantToAdd,
      atcItemThresholdValue
    } = this.getConditionalAtcItemSettings();

    if (!enableConditionalAtcItem) {
      return;
    }

    let relevantCount = 0;
    let relevantTotal = 0;

    parsedState.items.forEach((item) => {
      if (atcItemVariantToAdd.includes(String(item.variant_id))) {
        return;
      }
      const productIdStr = String(item.product_id);
      let productEligible;

      if (atcItemProductList.length === 0) {
        productEligible = true;
      } else if (useAsExclusion) {
        productEligible = !atcItemProductList.includes(productIdStr);
      } else {
        productEligible = atcItemProductList.includes(productIdStr);
      }

      if (!productEligible) return;

      if (atcItemConditionType === 'cart_item_count') {
        relevantCount += item.quantity;
      } else if (atcItemConditionType === 'cart_subtotal') {
        relevantTotal += item.original_line_price;
      } else if (atcItemConditionType === 'cart_total') {
        relevantTotal += item.final_line_price;
      }
    });

    let conditionMet = false;
    if (atcItemConditionType === 'cart_item_count') {
      conditionMet = relevantCount >= atcItemThresholdValue;
    } else {
      const requiredCents = atcItemThresholdValue * 100;
      conditionMet = relevantTotal >= requiredCents;
    }

    if (!conditionMet) {
      window.userRemovedAutoAtcItem = false;
      localStorage.removeItem('userRemovedAutoAtcItemData');
    }

    if (conditionMet && window.userRemovedAutoAtcItem) {
      return;
    }

    const singleVariantId = atcItemVariantToAdd[0];
    if (!singleVariantId) {
      return;
    }
    const lineIndex = this.findLineIndexByVariantId(parsedState.items, singleVariantId);
    if (conditionMet) {
      if (lineIndex === -1) {
        this.forceCartAdd(singleVariantId, 1);
      }
    } else {
      if (lineIndex !== -1) {
        this.forceCartRemove(lineIndex + 1);
      }
    }
  }

  findLineIndexByVariantId(items, variantId) {
    return items.findIndex((it) => String(it.variant_id) === String(variantId));
  }

  forceCartAdd(variantId, quantity) {
    this.isForcedCartUpdate = true;

    const body = JSON.stringify({
      items: [{ id: variantId, quantity }]
    });

    fetch(`${routes.cart_add_url}`, { ...fetchConfig('json'), ...{ body } })
      .then((response) => response.json())
      .then((jsonData) => {
        this.refreshCartAfterForcedUpdate();
      })
      .catch((err) => {
        this.isForcedCartUpdate = false;
      });
  }

  forceCartRemove(line) {
    this.isForcedCartUpdate = true;

    const body = JSON.stringify({
      line,
      quantity: 0
    });

    fetch(`${routes.cart_change_url}`, { ...fetchConfig(), ...{ body } })
      .then((response) => response.text())
      .then((state) => {
        this.refreshCartAfterForcedUpdate();
      })
      .catch((err) => {
        this.isForcedCartUpdate = false;
      });
  }

  refreshCartAfterForcedUpdate() {
    const isDrawer = (this.tagName === 'CART-DRAWER-ITEMS');
    const sectionId = isDrawer ? 'cart-drawer' : 'main-cart-items';

    fetch(`${routes.cart_url}?section_id=${sectionId}`)
      .then((resp) => resp.text())
      .then((responseText) => {
        const html = new DOMParser().parseFromString(responseText, 'text/html');

        if (isDrawer) {
          const newDrawerItems = html.querySelector('cart-drawer-items');
          const newDrawerFooter = html.querySelector('.cart-drawer__footer');
          const currentDrawerItems = document.querySelector('cart-drawer-items');
          const currentDrawerFooter = document.querySelector('.cart-drawer__footer');
          if (newDrawerItems && currentDrawerItems) {
            currentDrawerItems.replaceWith(newDrawerItems);
          }
          if (newDrawerFooter && currentDrawerFooter) {
            currentDrawerFooter.replaceWith(newDrawerFooter);
          }
        } else {
          const newCartItems = html.querySelector('cart-items');
          if (newCartItems) {
            this.innerHTML = newCartItems.innerHTML;
          }
        }
      })
      .catch((err) => {})
      .finally(() => {
        this.isForcedCartUpdate = false;
      });
  }

Browse other ways to boost conversion rate & profit

Here's 4 Ways We Can Help You

Here's 4 ways we can help you:

  1. Get our high-converting theme that generated us $4.3M in sales: Click here
  2. Find Conversion Leaks that cost you orders and profit: Click here
  3. Work with us to add $10,000 to your store in 10 weeks. Send an email to hello [at] theprompted.co with the subject line “10k”
  4. Email us to work privately on custom development. Send an email to hello [at] theprompted.co and use the subject line "custom", and then detail out what you have in mind, including all specifics.