This can be done with JavaScript, but keep in mind that client-side validation is only for convenience. Shopify will still validate inventory server-side at checkout, so the script mainly prevents customers from selecting a quantity higher than the available stock.
The general approach is:
Listen for variant changes.
Read the selected variant’s inventory_quantity.
Set the quantity input’s max attribute to that value.
If the customer has already entered a higher quantity, automatically reduce it to the available stock.
If you’re using the Horizon theme, the exact event name and quantity selector markup may differ from othersr Shopify themes. The snippet may need to be adapted to Horizon’s JavaScript and DOM structure.
If you’re able to share:
your product page URL, or
the main-product.liquid (or the quantity selector code),
Someone can provide a version that plugs directly into the Horizon theme without additional modifications.
“Thank you! I use the Horizon theme. Could you please adapt the snippet specifically for Horizon? The event name and quantity selector may be different from other themes.”
{%- comment -%} Limit the quantity to the selected variant's stock {%- endcomment -%}
<script>
(function () {
var STOCK = {
{%- assign first = true -%}
{%- for v in product.variants -%}
{%- if v.inventory_management != blank and v.inventory_policy != 'continue' and v.inventory_quantity > 0 -%}
{%- assign cap = v.inventory_quantity -%}
{%- if v.quantity_rule.max != blank and v.quantity_rule.max < cap -%}{%- assign cap = v.quantity_rule.max -%}{%- endif -%}
{%- unless first -%},{%- endunless -%}"{{ v.id }}": {{ cap }}{%- assign first = false -%}
{%- endif -%}
{%- endfor -%}
};
function variantId() {
var el = document.querySelector('product-form-component input[name="id"], form[action*="/cart/add"] input[name="id"], input[name="id"]');
return el ? el.value : null;
}
function apply() {
var id = variantId();
document.querySelectorAll('quantity-selector-component').forEach(function (qs) {
var input = qs.querySelector('input[name="quantity"]');
if (!input) return;
var min = input.getAttribute('min') || '1';
var step = input.getAttribute('step') || '1';
var cap = STOCK.hasOwnProperty(id) ? String(STOCK[id]) : null;
if (typeof qs.updateConstraints === 'function') qs.updateConstraints(min, cap, step);
else if (cap) { input.setAttribute('max', cap); if (parseInt(input.value, 10) > +cap) input.value = cap; }
else input.removeAttribute('max');
});
}
document.addEventListener('shopify:product:select', function (e) {
if (e && e.promise && e.promise.then) e.promise.then(apply, apply);
setTimeout(apply, 350);
});
if (document.readyState !== 'loading') apply();
else document.addEventListener('DOMContentLoaded', apply);
})();
</script>
The quantity now stops at that variant’s stock, the plus button greys out at the limit, and it re-caps every time the shopper changes variant.
Note
The block sits in the product template, so it covers every product on that template.
Sold-out variants are already blocked by the Add to cart button, so those are left alone.
If a variant allows overselling, it stays unlimited on purpose.
Limitations:
Note: the “up to 5 in stock” (for example) limit is fixed when the page loads. If someone else buys some of that variant meanwhile, your page still lets you pick up to 5 until you refresh. Shopify catches it at checkout, so you can’t oversell, but the on-page number can be stale until then.
“Thank you! I use the Horizon theme. Could you please adapt the snippet specifically for Horizon? The event name and quantity selector may be different from other themes.”
Thank a lot, good solution with liquid bloc in product not in general code, easy to do.