Skip to content

Purchase limits (quantity limits and order limits)

Purchase limits are quantity limits and order limits for your store. They set how many of an item customers can buy: at least 6, at most 2 per order, at most 1 per customer, or only in packs of 12. They also set a minimum order or a maximum order, by number of items, by subtotal or by weight. Shopify checks them itself, in checkout and in express checkouts like Shop Pay, Apple Pay and Google Pay, so customers can’t get around them.

Plan: Deals & Rules or Buddy. Works on all Shopify plans. In Shopify admin it appears under Settings → Checkout → Checkout rules.

You want Set
A quantity limit, like at most 2 per order Maximum on products, tags or a collection
A minimum quantity, like at least 6 Minimum on products, tags or a collection
Packs, like only in 12s Pack size
A minimum order, like $50 Whole order, Order subtotal, Minimum subtotal
A limit per customer, like 1 each Count purchases set to Per customer, all time, or per year, month or day (see per-customer limits)
A wholesale minimum A minimum for Logged-in customers with tags or B2B customers
  1. In Checkout Buddy, open Customizations. On the Checkout rules tab, click Create customization and choose Purchase limits.

  2. In the Limit card, click a template under Start from a template, or fill the limit in yourself:

    • A quantity limit, like at most 2 per order: under Applies to, keep Specific products and pick them (or choose Product tags or Collections), then enter 2 under Maximum.
    • A minimum order, like $50: under Applies to, choose Whole order. Set Limit to Order subtotal and enter 50 under Minimum subtotal.
    • Packs, like only in 12s: pick the items, then enter 12 under Pack size.
  3. Change the messages if you like. What buyers see shows each message with your numbers and products filled in.

  4. Click Save. Checkout checks the limit from the first save; turn it off under Status.

  5. To show the limit before checkout too, turn on the notices for product and cart pages.

The Limit card for at most 2 per order: Count purchases set to In one order, Maximum 2, the message over the maximum, and What buyers see reading ‘You can buy at most 2 of Rose candle.’

A new limit starts with Start from a template. A template fills in the limit; you pick the products and can change anything after.

Template What it fills in
2 per order At most 2 of each product in one order
Packs of 12 Items sell in multiples of 12
1 per customer Each customer buys the item once, ever. Also needs the workflows in per-customer limits
Order minimum The whole order’s subtotal is at least $50
Wholesale minimum Customers tagged wholesale order $250 or more

The same templates are cards under Templates in Create customization: Rules and limits has Order minimum, 2 per order, 1 per customer and Packs of 12, and Wholesale has Wholesale minimum and Company order minimum (B2B), where company buyers order $500 or more.

  • Limited drop: Specific products, Each product separately, Maximum 2.
  • Case packs: the Wine collection, All selected items together, Pack size 6.
  • Order minimum: Whole order, Order subtotal, Minimum 50.
  • At most 10 items per order: Whole order, Number of items, Maximum 10.
  • Retail limit, wholesale exempt: Specific products, Maximum 3, Visitors without tags wholesale.
  • Wholesale minimum: Whole order, Order subtotal, Minimum 500, Logged-in customers with tags wholesale.
  • Case packs for companies: Specific products, Pack size 12, B2B customers.
  • Courier weight cap: Whole order, Weight, Maximum 30 kg.
  • Sample box: the Samples collection, All selected items together, Different products 5.
  • Launch week drop: Specific products, Maximum 2, Logged-in customers only, Only between dates.
  • International minimum: Whole order, Order subtotal, Minimum 100, Everywhere except your country.

Each limit is its own card. Click Add limit for more, up to 25 in one Purchase limits.

  • Applies to: Specific products (pick products or single variants), Product tags, Collections, or Whole order.
  • Leave out some items: products, tags or collections the limit doesn’t count, like gift cards in an order minimum or one bestseller in a collection’s limit.
  • Count (products, tags and collections):
    • Each product separately: sizes and colors of a product add up. “At most 2” allows 2 of each product.
    • Each variant separately: each size or color counts on its own.
    • All selected items together (mix and match): any mix counts. “Packs of 6” from a wine collection allows 4 Merlot and 2 Rosé.
  • Count purchases (number of items): In one order, or Per customer over all time, per year, per month or per day, which adds what the customer bought before. Not for limits that pick or leave out collections. See Per-customer limits.
  • Limit: Number of items, Weight (from each variant’s weight in Shopify, in the unit you pick), or for the whole order Order subtotal.
  • Minimum, Maximum, Pack size, Different products: fill in any of them, and leave the others empty. For a subtotal or a weight, the first two read Minimum subtotal and Maximum subtotal, or Minimum weight and Maximum weight.
    • A pack size allows only multiples, like 6, 12 or 18. It’s for numbers of items of products, tags and collections; for packs across an order, use a collection with All selected items together.
    • Different products (mix and match and the whole order) caps how many different products there can be, like a sample box of up to 5.
  • Messages: what customers see for each limit you set. Leave one blank for the default shown in the box. You can use {{product}}, {{min}}, {{max}}, {{multiple}} and {{quantity}} (how many are in the cart, the weight or the subtotal). What buyers see, under the messages, shows each one with your numbers and products filled in.
  • Translate these messages: shows when your store has other languages. Add a message per language, or leave it blank for your message above, or our default in French, German, Spanish, Italian, Dutch, Portuguese or Japanese.
  • Who it applies to: visitors are everyone; customers are logged in; guests aren’t.
    • All visitors.
    • Logged-in customers only: guests are asked to log in to buy these items (“Log in to buy …”). Use it for drops, or with limits per customer.
    • Logged-in customers with tags: customers with any of the tags in Customer tags, like wholesale. Guests have no tags, so they’re left out.
    • Visitors without tags: guests, and customers without the tags.
    • B2B customers: customers buying for a company in Shopify B2B.
    • Non-B2B visitors: guests, and customers not buying for a company.
  • Only in some countries or regions: only in, or everywhere except, the countries, states and provinces you add. The country is the one the buyer shops in; a state or province is known once checkout has a shipping address.
  • Only between dates: the first and last day the limit applies, in your store’s time zone. Leave one blank for no start or no end.

The Cart card has Block Add to cart over a maximum, on by default: adding more than a maximum to the cart is refused. Checkout checks every limit either way. See when limits are checked.

  • Maximums (and Different products) are checked when customers change their cart and at checkout. Adding more than the maximum to the cart, or raising a quantity past it, is refused with your message; how the cart page shows it depends on your theme. If your theme’s cart doesn’t show the message, turn off Block Add to cart over a maximum: maximums then wait for checkout too.
  • Minimums and pack sizes are checked at checkout, so customers can fill their cart first. The message shows at the top of checkout, and they can’t pay until the cart meets the limit.
  • A minimum only counts what’s in the cart. “At least 6 of Merlot” doesn’t ask customers who didn’t add Merlot to buy any.

The Purchase limits app embed shows your limits in your online store before checkout:

  • Product pages: a line above Add to cart, like “Minimum 2 · Maximum 6 · Packs of 2”, “Up to 2 per customer”, or “Log in to buy” for guests. If the theme has a quantity box, it starts at the minimum and steps by the pack size.
  • Cart page: a “Before you check out” list of the limits the cart doesn’t meet yet, above the checkout button, like “Buy at least 2 of The Complete Snowboard.” It changes as the cart does.

To turn it on:

  1. In Checkout Buddy, click Turn on in the Online Store theme card on Home, or in the Product and cart pages card of any Purchase limits.
  2. Shopify’s theme editor opens on your live theme, with Purchase limits turned on under App embeds. Change its settings if you like (below), then click Save.
  3. Open a product that has a limit in your store to see its notice. Home now shows On for Purchase limits notices.

To turn it off, open the theme editor from the same card, turn off Purchase limits under App embeds and save.

Its settings turn each part on or off and set the look:

  • Show limits above Add to cart: the line on product pages.
  • Start the quantity at the minimum and step by the pack size: the product page’s quantity box.
  • List limits the cart doesn’t meet yet: the list on the cart page.
  • Text: leave it empty to use your theme’s text color (a little lighter), or pick one.
  • Background: none by default. With one, the product page line gets a little padding and rounded corners.
  • Cart list border: a light shade of the text color by default.
  • Text size: Small (the default), Same as the theme or Large.

For anything else, add CSS for body .cb-purchase-limits-notice (the product page line) or body .cb-purchase-limits-cart (the cart page list) in your theme editor’s Theme settings → Custom CSS. Start with body so your rule wins over the app’s own, for example body .cb-purchase-limits-cart { border-radius: 0; }.

  • Display only: checkout still enforces your limits, with or without the embed.
  • Languages: the cart page list uses your messages and their translations. Product page notices and the default messages come in English, French, German, Spanish, Italian, Dutch, Portuguese and Japanese, in the language the customer shops in.
  • Per-customer limits show on product pages, like “Up to 2 per customer”. The notice doesn’t count the customer’s earlier orders: Shopify counts them when the customer adds to the cart and at checkout.
  • States and provinces: they’re known only at checkout, so a limit for some states shows to everyone in that country.
  • Cart drawers: the list shows on the cart page only.

With Count purchases set to Per customer, a maximum counts the customer’s earlier orders too: 1 free sample per customer, 2 pairs per customer during a drop, or 10 items per customer per month. The limit is checked when they add to the cart and at checkout.

A product page with the message ‘You can buy at most 2 of The Complete Snowboard per customer.’ above Add to cart, after the customer already bought one and has one in the cart

  • Customers have to be logged in. Guests are asked to log in to buy the items, because their orders can’t be counted.
  • Two free Shopify Flow workflows keep each customer’s counts: one adds every new order, the other takes cancelled orders off. Add them once for your store; every per-customer limit uses them.
  • Counting starts when you turn the workflows on. Orders from before don’t count.

Setting it up takes about 2 minutes: set the limit, then import the two workflows.

In a limit card, set Count purchases to Per customer, all time, per year, per month or per day, then fill in Maximum per customer and save.

The Count purchases choices with Per customer, all time selected, the blue Needs the Purchase limits Flow workflow notice, and Maximum per customer set to 2

Install Shopify Flow if your store doesn’t have it.

  1. Download both workflows:

  2. Open Flow from Apps in Shopify admin. Click Import, choose purchase-limits-count-orders.flow and click Import.

    Flow’s Import workflow box with purchase-limits-count-orders.flow ready to import

  3. Click Turn on workflow, then Turn on.

  4. Go back to Workflows and do the same with purchase-limits-count-cancels.flow.

Import each file once. If you already built these workflows by hand, don’t import them as well: every order would count twice. To build them yourself instead, see build the workflows by hand.

  1. As a logged-in customer, buy an item that has a per-customer limit.

  2. In Flow, open Purchase limits: count orders. Under Recent runs, the order’s run shows Completed within a few seconds.

  3. The customer can now buy only what’s left. Going over is refused in the cart (as above) and at checkout:

    The top of checkout with the message You can buy at most 2 of The Complete Snowboard per customer.

  4. Cancel that order in Shopify admin. Purchase limits: count cancels runs, and the customer can buy the item again.

  • Two orders placed seconds apart can both get through, because Flow counts each order after it’s placed.
  • Refunds don’t lower the count; cancel the order if it shouldn’t count.
  • Collections: per-customer limits can’t pick or leave out collections, which Shopify Flow can’t see. The editor turns those choices off. Use specific products or product tags; for a whole collection, tag its products.
  • Per year, month or day follow your store’s dates and time zone: a new month starts every customer’s monthly count from 0.
  • Amounts are in your store’s currency and converted for customers paying in another currency. Messages show the amount in their currency.
  • The order subtotal is after product discounts and before order discounts, shipping and taxes.
  • Messages show at the top of checkout, not next to the product. Up to 10 show at once. To show limits on product and cart pages too, see Product and cart pages.
  • Big selections: Shopify allows about 600 picked variants across all the limits in one Purchase limits. For more, use product tags or a collection.
  • Moving to the Free plan deletes your purchase limits. See downgrade to Free.
  • Not checked outside checkout: Shopify POS, subscription renewals and orders that apps create for you don’t go through checkout’s rules (a Shopify limit).
  • If Shopify can’t run the limits, checkout goes on without them rather than stopping every customer. Very big carts, around 200 different items, checked against many product tag and collection limits can be too much for Shopify’s checkout functions; split such limits into a second Purchase limits.

The two workflows of per-customer limits, made step by step instead of imported. Open Flow from Apps in Shopify admin.

The workflow that counts orders

  1. Click Create workflow, then Select a trigger. Search for Order created and pick it.

  2. Click the + under Order created, then Action. Search for Run code and pick it.

  3. Give the code the two metafields it reads. In Select inputs from previous steps, click Add variable and add each of these:

    • The customer’s counts: click order, search for metafield, and click + next to the metafield under ./customer/. Click Use an unstructured metafield, enter checkout_buddy.purchase_counts in Namespace and key, and click Add. The variable is named purchaseCounts. Click value.
    • Your limits: click Add variable again, click shop, search for metafield, and click + next to the first metafield. Click Use an unstructured metafield, enter checkout_buddy.purchase_limits, and click Add. The variable is named purchaseLimits. Click value.

    Flow’s Create variable box with checkout_buddy.purchase_counts in Namespace and key and purchaseCounts as the new variable name

  4. Replace the three boxes with the code below. In each box, select all its text (Cmd+A or Ctrl+A) and paste:

    • Select inputs from previous steps: the Input.
    • Define outputs: the Output.
    • Write code: the Code.

    The Run code step after pasting: the input query on the left, the Output type below it, and the code on the right starting with const SIGN = 1

  5. Close Run code. Click the + under it, then Action. Search for Update customer metafield and pick it. Set:

    • Metafield: Use an unstructured metafield
    • Namespace: checkout_buddy
    • Key: purchase_counts
    • Type: JSON
    • Value: {{runCode.counts}}

    The Update customer metafield step with namespace checkout_buddy, key purchase_counts, type JSON and value runCode.counts

  6. Rename the workflow, for example Purchase limits: count orders (More actions → Rename). Click Turn on workflow, then Turn on.

The finished workflow: Order created, then Run code, then Update customer metafield checkout_buddy.purchase_counts

The workflow that takes cancelled orders off

  1. In the first workflow, click More actions → Duplicate and name the copy Purchase limits: count cancels.
  2. Open the copy from the Workflows list and click Edit. Point at the Order created step and click the swap button next to it. Search for Order cancelled and pick it.
  3. Click Run code and change the first line of the code to const SIGN = -1;.
  4. Click Turn on workflow, then Turn on.

Input

{
order {
createdAt
lineItems {
quantity
variant {
id
}
product {
id
tags
}
}
customer {
purchaseCounts {
value
}
}
}
shop {
timezoneOffsetMinutes
purchaseLimits {
value
}
}
}

Output

type Output {
counts: String!
}

Code

const SIGN = 1;
const parse = (value) => {
try {
return JSON.parse(value || '') || {};
} catch {
return {};
}
};
// The store's date of a time like 2026-11-03T00:22:02Z, from the store's offset in
// minutes (New York: -300). Plain arithmetic: Run code blocks clock functions, Date too
const MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const isLeapYear = (year) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
const daysInMonth = (year, month) =>
month === 2 && isLeapYear(year) ? 29 : MONTH_DAYS[month - 1];
const localDate = (time, offsetMinutes) => {
const text = String(time || '');
const parts =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::[\d.]+)?(Z|([+-])(\d{2}):?(\d{2}))?/.exec(
text,
);
if (!parts) {
return text.slice(0, 10);
}
let [year, month, day] = [parts[1], parts[2], parts[3]].map(Number);
// Minutes into the day in UTC, then in the store's time zone
const zone = parts[7]
? (parts[7] === '-' ? -1 : 1) * (Number(parts[8]) * 60 + Number(parts[9]))
: 0;
let minutes =
Number(parts[4]) * 60 + Number(parts[5]) - zone + (Number(offsetMinutes) || 0);
for (; minutes < 0; minutes += 1440) {
day -= 1;
if (day < 1) {
month -= 1;
if (month < 1) {
month = 12;
year -= 1;
}
day = daysInMonth(year, month);
}
}
for (; minutes >= 1440; minutes -= 1440) {
day += 1;
if (day > daysInMonth(year, month)) {
day = 1;
month += 1;
if (month > 12) {
month = 1;
year += 1;
}
}
}
const pad = (value) => String(value).padStart(2, '0');
return `${year}-${pad(month)}-${pad(day)}`;
};
// gid://shopify/ProductVariant/123 to 123
const idNumber = (gid) =>
Number(
String(gid || '')
.split('/')
.pop(),
) || 0;
const matches = (limit, line) => {
const tags = (line.tags || []).map((tag) => String(tag).trim().toLowerCase());
const hasTag = (list) =>
(list || []).some((tag) => tags.includes(String(tag).trim().toLowerCase()));
const picked =
limit.selectionType === 'ALL' ||
(limit.selectionType === 'VARIANTS' &&
(limit.variantIds || []).includes(line.variant)) ||
(limit.selectionType === 'PRODUCT_TAGS' && hasTag(limit.productTags));
const leftOut =
(limit.excludeType === 'VARIANTS' &&
(limit.excludeVariantIds || []).includes(line.variant)) ||
(limit.excludeType === 'PRODUCT_TAGS' && hasTag(limit.excludeProductTags));
return picked && !leftOut;
};
const countsKey = (limit, line) => {
if (limit.countBy === 'PRODUCT') {
return `${limit.key}:p${line.product}`;
}
if (limit.countBy === 'VARIANT') {
return `${limit.key}:v${line.variant}`;
}
return limit.key;
};
function countPurchases(input, sign = SIGN) {
const order = input?.order || {};
const stored = parse(order.customer?.purchaseCounts?.value);
const counts = stored.c && typeof stored.c === 'object' ? stored.c : {};
const storedDate = String(stored.d || '');
const orderDate = localDate(order.createdAt, input?.shop?.timezoneOffsetMinutes);
const limits = (parse(input?.shop?.purchaseLimits?.value).limits || []).filter(
(limit) => limit && limit.key && limit.period && limit.period !== 'ORDER',
);
// Whether a date is in the counts' year, month and day
const samePeriods = (orderDay) =>
[4, 7, 10].map(
(length) =>
!!storedDate && storedDate.slice(0, length) === orderDay.slice(0, length),
);
let date = storedDate;
// The periods the order's items count in: all time, year, month, day
let inPeriod = [true, true, true, true];
if (sign > 0 && (!storedDate || orderDate >= storedDate)) {
// A new order: the periods it's in start over where they changed
const same = samePeriods(orderDate);
Object.keys(counts).forEach((key) => {
[1, 2, 3].forEach((index) => {
if (!same[index - 1] && Array.isArray(counts[key])) {
counts[key][index] = 0;
}
});
});
date = orderDate;
} else {
// A cancelled order, or an older one counted late: only the periods still current
inPeriod = [true, ...samePeriods(orderDate)];
}
(order.lineItems || []).forEach((item) => {
const line = {
variant: idNumber(item?.variant?.id),
product: idNumber(item?.product?.id),
tags: item?.product?.tags || [],
};
const quantity = Number(item?.quantity) || 0;
if (!line.variant || quantity <= 0) {
return;
}
limits
.filter((limit) => matches(limit, line))
.forEach((limit) => {
const key = countsKey(limit, line);
const count = (counts[key] = Array.isArray(counts[key])
? counts[key]
: [0, 0, 0, 0]);
inPeriod.forEach((counted, index) => {
if (counted) {
count[index] = Math.max(0, (Number(count[index]) || 0) + sign * quantity);
}
});
});
});
// Keys with nothing left go, to stay within the 10,000 bytes Shopify passes on
Object.keys(counts).forEach((key) => {
if (!Array.isArray(counts[key]) || counts[key].every((value) => !value)) {
delete counts[key];
}
});
return { counts: JSON.stringify({ d: date || orderDate, c: counts }) };
}
export default function main(input) {
return countPurchases(input, SIGN);
}