How to Enforce a Minimum Order Quantity at Checkout
To require a minimum number of units in a ShopsWired checkout, use a plugin's checkout.before_create hook, sum ctx.data.order.items[].qty, and throw an error when the total is too small. This checks the order before it is saved. A message on the product page helps shoppers understand the policy; the checkout hook enforces it.
Consider a store selling small stationery items that only wants to pack orders containing at least six units. Customers can mix designs and colors. One notebook and five cards qualify; five notebooks do not. That precise rule is small enough to implement without catalog lookups, external services, or a pricing calculation.
Decide what the minimum actually means
Write the acceptance rule before writing the plugin. Six units across a basket, six of each product, and full cases of six are different requirements. This example implements only the first: every order line contributes its quantity, and any total of six or more passes. Seven units are allowed. It does not require multiples of six.
It also counts every product equally. A digital download contributes a unit, just as a notebook does. If that would let customers bypass your intended packing minimum, this exact example is the wrong policy for your catalog. Decide which products qualify and how to identify them before adding exceptions. Do not quietly ship a broad rule and discover the exclusions through customer complaints.
If the goal is simply to offer a lower unit price when someone buys more, ShopsWired already has native quantity price breaks at product or variant level. Named customer price levels handle wholesale pricing. Those are pricing tools; the plugin below is an acceptance rule. Keep the two decisions separate so a discount change cannot accidentally redefine which baskets you accept.
Register one hook script
Building your own plugin requires Pro or higher. Starter runs a full store with ready-made themes and plugins; it is not the plan for developing this custom rule. Work on a development store first, since activating a checkout blocker changes which orders customers can place.
The plugin needs two files. Put this in manifest.json:
{
"id": "minimum-order-quantity",
"name": "Minimum Order Quantity",
"version": "1.0.0",
"scripts": [
{ "path": "hooks.js" }
]
}The manifest names the script file. Hook names are detected from that file's module.exports; there is no separate list of events to maintain in the manifest. Keep this file focused on checkout validation so the purpose is obvious to the next developer who opens it.
Count quantities, then reject the order
Put the following in hooks.js. The first argument is ctx, and this hook wraps the order inside ctx.data.order. The line-item quantity field is qty.
module.exports = {
"checkout.before_create": function (ctx) {
const minimum = 6;
const items = ctx.data.order.items;
let units = 0;
for (let i = 0; i < items.length; i++) {
units += items[i].qty;
}
if (units < minimum) {
const missing = minimum - units;
throw {
error: "Orders require at least " + minimum +
" units. Please add " + missing +
" more " + (missing === 1 ? "unit" : "units") +
" to your cart."
};
}
}
};items.length would count lines, not units. A single line with quantity six must pass, even though the array contains only one entry. Summing qty also makes mixed variants work naturally: quantities two and four pass whether they belong to the same product or different ones.
The handler has one failure action: throwing an object with an error message, the documented checkout-blocking pattern. When the quantity is sufficient, it finishes without changing the order. It does not raise quantities automatically, alter prices, or add items the shopper did not choose.
The minimum is deliberately a constant. Change it in one place and the error message follows. If you later expose a merchant-editable setting, validate that setting as a positive whole number and decide what happens when it is missing. A configurable rule with an ambiguous default is harder to support than a fixed rule everyone understands.
Make the policy visible before checkout
Enforcement should not be the customer's first encounter with the minimum. State the policy near the buying controls and in the cart: orders require six units total, and customers may mix products. Use the same wording in support replies. A shopper who understands the rule can adjust the basket without guessing.
The handler's error tells the shopper how many units are missing. For a five-unit basket it asks for one more unit, rather than showing a generic failure. If your storefront serves multiple languages, the literal English message in this example also needs an appropriate translation strategy before release.
A disabled checkout button can be useful feedback, but browser behavior alone is not an enforcement boundary. Keep the server-side check even if the theme displays a live quantity counter. Test the message in your actual checkout theme: this code provides the rejection, not a new cart interface.
Test the boundary and the recovery
Test both rejection and successful correction. The most useful cases are small and easy to inspect:
- One line, quantity five: checkout is rejected with a request for one more unit.
- One line, quantity six: this hook permits checkout.
- Two lines, quantities two and four: this hook permits checkout.
- Two lines, quantities two and three: checkout is rejected.
- Seven units: this hook permits checkout; there is no case-multiple restriction.
- Corrected basket: after a rejection, add the missing unit and submit again.
Repeat the relevant cases as a guest and a signed-in customer. This rule has no customer exception. Also check the store's enabled delivery and payment choices: the code does not exempt pickup or pay-in-store orders. A passing result means only that this hook accepts the quantity; stock, payment, and other checkout checks still matter.
Know where this rule stops
The published reference explicitly says checkout.before_create does not run on subscription renewals. Do not promise that this plugin enforces a six-unit minimum on every recurring invoice. Likewise, a checkout example is not proof of coverage for staff-created orders or orders written by another integration. Treat those as separate workflows to specify and verify.
This is also the wrong example for a minimum order value, product-specific pack sizes, or a wholesale-only rule. Those policies need different inputs and tests. Keep this version only when a basket-wide unit count describes the business requirement exactly.
The developer documentation covers the manifest, checkout hook contract, and order item fields used here. Start with the smallest rule you can explain, test its boundary on a development store, then confirm the shopper can recover from a rejection before activating it on the store taking orders.