Where you can achieve what your competitors can’t


Limit Quantity Per Customer In Shopify

This custom code is completely free to use, but please note it does not include 1-on-1 technical support. If you need a bespoke, done-for-you solution to customize your Shopify store, my team is here to help! Head to my channel homepage and use the link in the trailer to reach out.

{%- assign button_selector = '.product-form-buttons, .quantity-selector-wrapper, quantity-selector-component, add-to-cart-component, .accelerated-checkout-block, .product-form__input--quantity, .product-form__quantity, .product-form--quantity, quantity-input, .quantity, .product-form__buttons, .shopify-payment-button, button[name="add"], .product-form--atc, .product-form--payment-button, .product-form-container, .product-form__item--quantity' -%}
{%- assign pbw_accent = '#1a1a1a' -%}
{%- assign pbw_variant = 'filled' -%}

{%- assign assigned_rule = customer.metafields.custom.assigned_rule.value -%}

{%- if customer -%}
  {%- if assigned_rule -%}
    {%- assign min_qty = assigned_rule.minimum | default: 1 -%}
    {%- assign max_qty = assigned_rule.maximum | default: 9999 -%}
    {%- assign step_qty = assigned_rule.increment | default: 1 -%}
    {%- assign rule_name = assigned_rule.title -%}
  {%- else -%}
    {%- assign min_qty = 1 -%}
    {%- assign max_qty = 999 -%}
    {%- assign step_qty = 1 -%}
    {%- assign rule_name = 'Standard' -%}
  {%- endif -%}
{%- endif -%}

{%- if customer -%}
  {%- assign pbw_heading = 'Your Purchase Limit: ' | append: max_qty | append: ' units' -%}
  {%- assign pbw_subtext = 'Account Tier: ' | append: rule_name -%}
  {%- assign pbw_icon = 'tag' -%}
  {%- assign hide_buttons = false -%}
{%- else -%}
  {%- assign pbw_heading = 'Login to Purchase' -%}
  {%- assign pbw_subtext = 'This item has exclusive quantity limits per customer.' -%}
  {%- assign pbw_icon = 'spark' -%}
  {%- assign hide_buttons = true -%}
{%- endif -%}

{%- if hide_buttons -%}
<style>
  {{ button_selector }} { 
    display: none !important; 
    visibility: hidden !important; 
    opacity: 0 !important;
    pointer-events: none !important; 
  }
</style>
{%- endif -%}

<aside class="pbw pbw--{{ pbw_variant }} pbw--align-left" role="note">
  {%- if pbw_icon != 'none' -%}
    <span class="pbw__icon" aria-hidden="true">
      {%- case pbw_icon -%}
        {%- when 'tag' -%}
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20.59 13.41 13.42 20.58a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>
        {%- when 'spark' -%}
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v3M12 18v3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M3 12h3M18 12h3M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1"/></svg>
      {%- endcase -%}
    </span>
  {%- endif -%}

  <div class="pbw__content">
    <p class="pbw__heading">
      {%- if customer -%}
        {{ pbw_heading }}
      {%- else -%}
        <a href="{{ routes.account_login_url }}" style="color: inherit; text-decoration: underline;">{{ pbw_heading }}</a>
      {%- endif -%}
    </p>
    {%- if pbw_subtext != blank -%}
      <p class="pbw__subtext">{{ pbw_subtext }}</p>
    {%- endif -%}
  </div>
</aside>

{%- if customer -%}
<script>
document.addEventListener('DOMContentLoaded', () => {
  const qtyInputs = document.querySelectorAll('input[name="quantity"]');
  if (qtyInputs.length === 0) return;

  qtyInputs.forEach(qtyInput => {
    qtyInput.setAttribute('min', '{{ min_qty }}');
    qtyInput.setAttribute('max', '{{ max_qty }}');
    qtyInput.setAttribute('step', '{{ step_qty }}');

    const enforceLimits = () => {
      let val = parseInt(qtyInput.value);
      if (val > {{ max_qty }}) qtyInput.value = {{ max_qty }};
      if (val < {{ min_qty }}) qtyInput.value = {{ min_qty }};
    };

    qtyInput.addEventListener('change', enforceLimits);
    qtyInput.addEventListener('input', enforceLimits);
  });
});
</script>
{%- endif -%}

<style>
  .pbw {
    --pbw-accent: {{ pbw_accent }};
    --pbw-bg: color-mix(in srgb, var(--pbw-accent) 6%, transparent);
    --pbw-border: color-mix(in srgb, var(--pbw-accent) 22%, transparent);
    --pbw-radius: 8px;
    --pbw-pad-block: 14px;
    --pbw-pad-inline: 16px;
    --pbw-gap: 12px;
    display: flex; 
    align-items: center; 
    gap: var(--pbw-gap); 
    padding: var(--pbw-pad-block) var(--pbw-pad-inline);
    margin-block: 16px; 
    border-radius: var(--pbw-radius); 
    border: 1px solid transparent; 
    line-height: 1.4; 
    box-sizing: border-box;
    font-family: inherit;
  }
  .pbw--filled { background: var(--pbw-bg); }
  .pbw--outlined { background: transparent; border-color: var(--pbw-border); }
  .pbw .pbw__icon { flex: 0 0 auto; width: 20px; height: 20px; color: var(--pbw-accent); }
  .pbw .pbw__icon svg { width: 100%; height: 100%; display: block; }
  .pbw .pbw__content { flex: 1 1 auto; min-width: 0; }
  .pbw .pbw__heading { margin: 0; font-size: 0.95em; font-weight: 600; color: inherit; }
  .pbw .pbw__subtext { margin: 2px 0 0; font-size: 0.85em; opacity: 0.8; color: inherit; }
</style>