Choose how often you want your deliveries, select your preferred grind type and coffees. We’ll help build a subscription that suits your routine.
Choose your delivery frequency
Choose how often you’d like your coffee delivered. You can choose bag sizes for each coffee in the next step.
Do you need your coffee ground?
Whole beans are best if you have a grinder. Choose a ground option if you need us to grind it for your brew method.
Choose your coffees
Select a bag size for each coffee, then add it to your subscription.
Il Caramello Blend
Chocolate, caramel, almond
A smooth, balanced coffee for everyday brewing.
Tall, Dark & Handsome Blend
Dark chocolate, buttery nougat
Velvety chocolate and smooth buttery nougat.
Guatemalan Single Origin
Roast hazelnut, chocolate, red apple
Single origin perfect balanced acidity and milk chocolate flavours
Papua New Guinean Single Origin
Dark chocolate, fig, spices
Full-bodied with medium acidity - equally delicious black or with milk
Decaf Coffee
Creamy chocolate & malt
Glacial mountain water washed and chemical free
No coffees are set up for this grind yet. Check the product block settings or variant setup.
Review your subscription
Check your coffees, delivery frequency, subscription price and order details before adding to cart.
Your coffees
Create a flexible coffee subscription based on how much coffee you drink, how you brew and the grind type you need. Choose your preferred coffee, select your delivery frequency and enjoy freshly roasted coffee delivered on your schedule.
${
unlocked
? 'Nice — you’ve unlocked free shipping on this subscription order.'
: `You’re ${formatMoney(remaining)} away from free shipping. Add another bag to unlock free shipping.`
}
`;
}
async function readLoopBundleDetails() {
if (!state.loopBundleId) {
throw new Error('Missing Loop Bundle ID.');
}
const url = `https://api.loopsubscriptions.com/storefront/2023-10/bundle/${state.loopBundleId}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'accept': 'application/json'
}
});
const json = await response.json();
console.log('Loop bundle details response:', json);
if (!response.ok || json.success === false) {
throw new Error(json.message || 'Could not load Loop bundle details.');
}
state.loopBundle = json.data || json;
return state.loopBundle;
}
async function createLoopBundleTransaction() {
setupLoopBundleDefaults();
if (!state.selectedBoxSizeId) {
throw new Error('Loop boxSizeId was not found. Check your Loop bundle setup.');
}
if (!state.selectedDiscountId) {
throw new Error('Loop discountId was not found. Check your Loop bundle setup.');
}
if (!state.sellingPlanShopifyId) {
throw new Error('Selling plan was not found for the selected frequency.');
}
if (!state.coffees.length) {
throw new Error('Please select at least one coffee before adding to cart.');
}
const selectedVariants = state.coffees.map(coffee => {
return {
title: coffee.title,
variantShopifyId: Number(coffee.variantId),
quantity: getCleanQuantity(coffee.quantity)
};
});
const totalQuantity = selectedVariants.reduce((sum, item) => {
return sum + Number(item.quantity || 0);
}, 0);
const payload = {
boxSizeId: String(state.selectedBoxSizeId),
discountId: String(state.selectedDiscountId),
sellingPlanShopifyId: Number(state.sellingPlanShopifyId),
quantity: totalQuantity,
productVariants: selectedVariants.map(item => ({
shopifyId: Number(item.variantShopifyId),
quantity: Number(item.quantity)
}))
};
console.log('GCB VERSION: loop-transaction-working-stable-drawer');
console.log('Create Loop transaction payload:', JSON.stringify(payload, null, 2));
const url = `https://api.loopsubscriptions.com/storefront/2023-10/bundle/${state.loopBundleId}/transaction`;
const response = await fetch(url, {
method: 'POST',
headers: {
accept: 'application/json',
'content-type': 'application/json'
},
body: JSON.stringify(payload)
});
const json = await response.json();
console.log('Loop transaction response:', JSON.stringify(json, null, 2));
if (!response.ok || json.success === false) {
throw new Error(json.message || 'Could not create Loop bundle transaction.');
}
const transactionId = json?.data?.txnId;
if (!transactionId) {
throw new Error('Loop transaction was created, but no txnId was returned.');
}
return transactionId;
}
async function addBundleToCart(transactionId) {
const items = state.coffees.map(coffee => {
return {
id: Number(coffee.variantId),
quantity: getCleanQuantity(coffee.quantity),
selling_plan: Number(state.sellingPlanShopifyId),
properties: {
_bundleId: String(transactionId),
_bundleVariantId: String(state.loopBundleId),
_bundleSource: 'guided_coffee_bundle',
_bagSize: String(coffee.size),
_grind: String(coffee.grind),
_frequency: String(state.frequency)
}
};
});
const payload = {
items,
sections: [
'cart-drawer',
'cart-icon-bubble'
],
sections_url: window.location.pathname
};
console.log('Shopify cart payload:', payload);
const response = await fetch('/cart/add.js', {
method: 'POST',
headers: {
accept: 'application/json',
'content-type': 'application/json'
},
body: JSON.stringify(payload)
});
const json = await response.json();
console.log('Shopify cart response:', json);
if (!response.ok) {
throw new Error(json.description || json.message || 'Could not add subscription bundle to cart.');
}
return json;
}
async function openCartDrawer(cartAddResponse) {
try {
sessionStorage.setItem('gcb_open_cart_drawer_after_reload', 'true');
window.location.href = window.location.href;
} catch (error) {
console.error('Could not refresh cart drawer:', error);
window.location.href='/cart';
}
}
function openCartDrawerAfterReload() {
const shouldOpen = sessionStorage.getItem('gcb_open_cart_drawer_after_reload');
if (shouldOpen !== 'true') return;
sessionStorage.removeItem('gcb_open_cart_drawer_after_reload');
setTimeout(() => {
const cartDrawer =
document.querySelector('cart-drawer') ||
document.querySelector('#CartDrawer') ||
document.querySelector('[data-cart-drawer]') ||
document.querySelector('.cart-drawer');
if (cartDrawer) {
if (typeof cartDrawer.open === 'function') {
cartDrawer.open();
return;
}
if (typeof cartDrawer.show === 'function') {
cartDrawer.show();
return;
}
cartDrawer.classList.add('active', 'is-open', 'animate');
cartDrawer.setAttribute('open', '');
cartDrawer.removeAttribute('aria-hidden');
document.body.classList.add('overflow-hidden');
document.body.classList.add('cart-drawer-open');
return;
}
const cartDrawerTrigger =
document.querySelector('[aria-controls="CartDrawer"]') ||
document.querySelector('[data-cart-drawer-toggle]') ||
document.querySelector('[data-cart-toggle]') ||
document.querySelector('a[href="/cart"]') ||
document.querySelector('button[name="cart"]');
if (cartDrawerTrigger) {
cartDrawerTrigger.click();
}
},1500);
}
root.addEventListener('click', async event => {
const button = event.target.closest('button');
if (!button) return;
if (button.dataset.progress) {
hideStatus();
const requestedStep = Number(button.dataset.progress);
if (!canNavigateToStep(requestedStep)) {
if (requestedStep === 2) {
showStatus('Choose your delivery frequency first.', 'error');
} else if (requestedStep === 3) {
showStatus('Choose your delivery frequency and grind first.', 'error');
} else if (requestedStep === 4) {
showStatus('Add at least one coffee before reviewing your subscription.', 'error');
}
return;
}
if (requestedStep === 3) {
updateAllProductCards();
}
if (requestedStep === 4) {
try {
setupLoopBundleDefaults();
updateFinalSummary();
} catch (error) {
showStatus(error.message, 'error');
return;
}
}
setStep(requestedStep);
return;
}
const currentStep = root.querySelector(`[data-step="${state.step}"]`);
if (button.hasAttribute('data-back')) {
hideStatus();
setStep(Math.max(1, state.step - 1));
return;
}
if (button.dataset.frequency) {
hideStatus();
clearSelectedOptions(currentStep);
button.classList.add('is-selected');
state.frequency = button.dataset.frequency;
state.selectedDeliveryInterval = button.dataset.deliveryInterval;
state.selectedDeliveryCount = button.dataset.deliveryCount;
state.monthlyMultiplier = Number(button.dataset.monthlyMultiplier || 0);
const plan = findSellingPlanByDelivery(
button.dataset.deliveryInterval,
button.dataset.deliveryCount
);
const sellingPlanId = getShopifySellingPlanId(plan);
if (!plan || !sellingPlanId) {
state.selectedSellingPlan = null;
state.sellingPlanShopifyId = null;
showStatus(
`Could not find a Loop selling plan matching ${state.frequency}.`,
'error'
);
return;
}
state.selectedSellingPlan = plan;
state.sellingPlanShopifyId = sellingPlanId;
state.brew = '';
state.grind = '';
setStep(2);
const grindStep = root.querySelector('[data-step="2"]');
if (grindStep) {
grindStep.querySelectorAll('.gcb-option').forEach(option => {
option.classList.remove('is-selected');
});
}
return;
}
if (button.dataset.grind) {
hideStatus();
clearSelectedOptions(currentStep);
button.classList.add('is-selected');
state.brew = button.dataset.brew;
state.grind = button.dataset.grind;
remapSelectedCoffeesForCurrentGrind();
updateAllProductCards();
setStep(3);
return;
}
if (button.hasAttribute('data-continue-grind')) {
hideStatus();
if (!state.grind) {
state.brew = 'I have a grinder';
state.grind = 'Whole beans';
}
updateAllProductCards();
setStep(3);
return;
}
if (button.dataset.cardSize) {
hideStatus();
const card = button.closest('[data-product-card]');
if (!card) return;
setCardSize(card, button.dataset.cardSize, button.dataset.cardGrams);
return;
}
if (button.hasAttribute('data-select-coffee')) {
hideStatus();
const card = button.closest('[data-product-card]');
setCoffeeQuantity(card, 1);
return;
}
if (button.hasAttribute('data-increase-coffee')) {
hideStatus();
const card = button.closest('[data-product-card]');
updateCoffeeQuantity(card, 1);
return;
}
if (button.hasAttribute('data-decrease-coffee')) {
hideStatus();
const card = button.closest('[data-product-card]');
updateCoffeeQuantity(card, -1);
return;
}
if (button.hasAttribute('data-next')) {
hideStatus();
try {
setupLoopBundleDefaults();
updateFinalSummary();
setStep(4);
} catch (error) {
showStatus(error.message, 'error');
}
return;
}
if (button.dataset.reviewIncrease) {
hideStatus();
const coffee = getCoffeeByVariantId(button.dataset.reviewIncrease);
if (coffee) {
coffee.quantity = getCleanQuantity(coffee.quantity) + 1;
const card = getCardByTitle(coffee.title);
if (card) updateProductCardDisplay(card);
updateFinalSummary();
}
return;
}
if (button.dataset.reviewDecrease) {
hideStatus();
const coffee = getCoffeeByVariantId(button.dataset.reviewDecrease);
if (coffee) {
coffee.quantity = getCleanQuantity(coffee.quantity) - 1;
if (coffee.quantity <= 0) {
state.coffees = state.coffees.filter(item => Number(item.variantId) !== Number(coffee.variantId));
}
const card = getCardByTitle(coffee.title);
if (card) updateProductCardDisplay(card);
updateFinalSummary();
}
return;
}
if (button.dataset.reviewRemove) {
hideStatus();
const coffee = getCoffeeByVariantId(button.dataset.reviewRemove);
if (coffee) {
state.coffees = state.coffees.filter(item => Number(item.variantId) !== Number(coffee.variantId));
const card = getCardByTitle(coffee.title);
if (card) updateProductCardDisplay(card);
updateFinalSummary();
}
return;
}
if (button.hasAttribute('data-add-more-coffee')) {
hideStatus();
updateAllProductCards();
setStep(3);
return;
}
if (button.hasAttribute('data-add-to-cart')) {
hideStatus();
try {
button.disabled = true;
button.textContent = 'Adding...';
const transactionId = await createLoopBundleTransaction();
const cartAddResponse = await addBundleToCart(transactionId);
showStatus('Subscription bundle added to cart.', 'success');
await openCartDrawer(cartAddResponse);
button.disabled = false;
button.textContent = 'Add subscription to cart';
} catch (error) {
console.error(error);
showStatus(error.message, 'error');
button.disabled = false;
button.textContent = 'Add subscription to cart';
}
}
});
async function init() {
try {
showStatus('Loading subscription options...', '');
await readLoopBundleDetails();
hideStatus();
updateProgressState();
console.log('Guided coffee bundle ready');
console.log('Loop Bundle ID:', state.loopBundleId);
console.log('Shopify domain:', state.myshopifyDomain);
} catch (error) {
console.error(error);
showStatus(error.message, 'error');
}
}
init();
openCartDrawerAfterReload();
})();
Subscription FAQS
How does subscription work?
Choose how often you want coffee delivered, select whether you want whole beans or ground coffee, then build your subscription by choosing your favourite coffees and bag sizes. Your subscription will be delivered and charged based on the frequency you select.
Can I choose different bag sizes in one subscription?
Yes. You can mix bag sizes in the same subscription. For example, you can choose one coffee as a 1kg bag and another coffee as a 250g or 500g bag.
Can I change the coffees in my subscription later?
Yes. You can change products anytime from your customer portal. You can also top up your order, skip, pause, delay or bring forward deliveries.
Is there a minimum subscription period?
Yes. There is a minimum of 3 orders before cancellation. You can still skip, pause, delay, bring forward, top up or change products from the start.
Can I skip or pause a delivery?
Yes. You can skip, pause, delay or bring forward deliveries anytime from your customer portal.
Can I get my coffee ground?
Yes. You can choose whole beans or select a grind option for your brew method, including espresso machine, stovetop, plunger, AeroPress, filter/drip or cold brew.
How many coffees are in each bag?
As a guide, a 250g bag makes around 15 coffees, a 500g bag makes around 31 coffees, and a 1kg bag makes around 62 coffees. This is based on approximately 16g of coffee per cup.
Do subscriptions qualify for free shipping?
Yes. Subscription orders over $99 qualify for free shipping. The builder will show your progress toward free shipping before checkout.
What is included with my coffee subscription?
Your subscription includes flexible coffee deliveries, 10% off, access to manage your subscription through the customer portal, and free access to our Kickstart Your Coffee Online Course.
Will I be charged monthly?
You’ll be charged based on the delivery frequency you choose. For example, if you choose delivery every 2 weeks, your subscription follows that selected frequency.
→
We’ve moved
Coffee Beans Delivered is now Grounds of Origin
Same coffee, same team — just a fresh name and home. Your orders and account are all here waiting for you.