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;
});
}