SOLVED: How to set up minimum order quantities for each product

Highlighted
Excursionist
30 0 5

This solution was dervied from various posts, and by studying the liquid references provided by Shopify. 

@VGC Unlimited posted a solution for a related issue on this topic: https://ecommerce.shopify.com/c/shopify-discussion/t/how-to-set-up-minimum-order-quantities-for-each...

Their solution, however, required that a customer order in groups of a certain number. For example, groups of 4 had to be purchased for thier solution to work. With a slight tweak, I was able to implement their solution and set a minimum qty number that didn't require the customer to order in groups of any amount, simply that the customer had to order the minimum qty of that item (inclusive of variants).

I am reposting their solution here, with the modification I made, so that others may use this for thier own purposes.

So first things first, install MetaFields Editor (completely free)

Setup a metafield. In our example we made the field namespace batching, the key batch_size, and the type an integer.

Next we updated the code in our cart (liquid.cart or another cart page you made), yours will look different, but here is the end result starting just above the for loop to populate the cart items.

{% assign varianttotals = "" %}
            {% for item in cart.items %}
            	{% assign this_id = item.product.id %}
            	{% assign this_qty = 0 %}
            	
            	{% for subitem in cart.items %}
                  {% if this_id == subitem.product.id %}
            		{% assign temp_qty = this_qty | plus: subitem.quantity %}
            		{% assign this_qty = temp_qty %}
            	  {% endif %}
              	{% endfor %}
            	{% assign varianttotals = varianttotals | append: this_qty %}
            	{% if forloop.index < forloop.length %}
            		{% assign varianttotals = varianttotals | append: "," %}
            	{% endif %}
            {% endfor %}
            {% assign varianttotals = varianttotals | split: "," %}
            
            {% assign batchesok = true %}
            {% for item in cart.items %}
            <tr>
              <td class="image">
                <div class="product_image">
                  <a href="{{ item.product.url }}">
                    <img src="{{ item.product.featured_image | product_img_url: 'small' }}"  alt="{{ item.product.title }}" />
                  </a>
                </div>
              </td>
              <td class="item">
                <a href="{{item.product.url }}">
                  <strong>{{ item.product.title }}</strong>
                  {% if item.product.variants.size > 1 %}
                  <span class="variant_title">{{ item.variant.title }}</span>
                  {% endif %}
                </a>
              </td>
              <td class="qty">
                <input type="text" size="4" name="updates[]" id="updates_{{ item.id }}" value="{{ item.quantity }}" onfocus="this.select();" class="tc item-quantity" />
              </td>
              <td class="errormessage">
                
              {% unless item.product.metafields.batching == empty %}
                {% assign index_i = forloop.index | minus: 1 %}
                {% assign batching = item.product.metafields.batching %}
                {% capture modulo %}{{ varianttotals[index_i] | modulo: batching.batch_size }}{% endcapture %}
                {% unless modulo == '0' %}
                	{% assign batchesok = false %}
                	Must order in multiples of <strong>{{ batching.batch_size }}</strong>
                {% endunless %}
              {% endunless %}
          	  </td>
              <td class="price">{{ item.line_price | money }}{{ varianttotal }}</td>
              <td class="remove"><a href="/cart/change?line={{ forloop.index }}&quantity=0" class="cart">Remove</a></td>
            </tr>
            {% endfor %}
            
            <tr class="summary">
              <td class="image">&nbsp;</td>
              <td>&nbsp;</td>
              <td>&nbsp;</td>
              <td>&nbsp;</td>
              <td class="price"><span class="total"><strong>{{ cart.total_price | money }}</strong></span></td>
              <td>&nbsp;</td>
            </tr>
          </tbody>
        </table>
        
        {% unless batchesok %}
        <div class="span12 errormessagebox" id="batch">
        {% for item in cart.items %}
        {% assign index_i = forloop.index | minus: 1 %}
        {% unless item.product.metafields.batching == empty %}
          {% assign batching = item.product.metafields.batching %}
          {% capture modulo %}{{ varianttotals[index_i] | modulo: batching.batch_size }}{% endcapture %}
          {% unless modulo == '0' %}
          <p>You must order <strong>{{ item.product.title }}</strong> in multiples of <strong>{{ batching.batch_size }}</strong>. Different sizes and colors of the same product all count toward this batch size.</p>
          {% endunless %}
        {% endunless %}
        {% endfor %}
        </div>
        {% endunless %}

        <div class="span6 inner-left inner-right">
          <div class="checkout-buttons clearfix">
            <label for="note">Add special instructions for your order...</label>
            <textarea id="note" name="note" rows="10" cols="50">{{ cart.note }}</textarea>
          </div>
        </div>
        
        <script>
          $(function(){
        	var $cartform = $('#cartform');

            $cartform.find('.item-quantity').change(function(){
              console.log("changed quantity");
              $cartform.find("#checkout").attr("disabled","true");
            });
          })
        </script>
		
        <div class="span6 cart-buttons inner-right inner-left">
          <div class="buttons clearfix">
            <input type="submit" id="checkout" class="btn" name="checkout" value="Check out" {% unless batchesok %}disabled{% endunless %}/>
            <input type="submit" id="update-cart" class="btn" name="update" value="Update" />
          </div>
        </div>

Now, this is where the modification I made comes in. Look for this line (there are 2 instances):

{% capture modulo %}{{ item.quantity | modulo: batching.batch_size }}{% endcapture %}
                {% unless modulo == '0' %}

Change it to:

{% capture modulo %}{{ varianttotals[index_i] | minus: batching.batch_size }}{% endcapture %}
          {% unless modulo >= '0' %}

In the old code, the modulo math filter is used to compare the varrianttotals value to the batch_size, which would return a remainder if the qty of items didn't evenly divide into the size of your batch you specified in your products metafield. If there was a remainder, then the customer would be shown an error message and the checkout button would have been disabled until the qty of items was a factor of the batch_size specified in your metafield.

In the new code, we simply change the the 'modulo' reference to a 'minus' reference, and then check if the comparison yields a number equal to or greater than 0. How does this work? As the code loops through the number of variants of the specific item you are trying to restrict, it's referencing the number of instances of the item and comparing it to the batch_size you specified. So, if the code only counts 2 instances of the item in your cart, and then subtracts it from your batch_size (let's say it's 4 for example) you specified in your metafield for the product, the result would be -2. Since -2 is not greater than 0, the error message would be shown and the chekcout button would be disabled. Once the customer adds 4 or more instances of the item to their cart, the error messages go away and the checkout button is re-enabled.

Here is the rest of VGC Unlimited's comments:

Next here is the css we changed (seriously just slapped it in on the end) yours will be different depending on how your cart looks and the way you want to display stuff:

.errormessage { color: #ff0000; width: 100px; }
.errormessagebox { border: 1px solid #ff0000; margin-left: 0; margin-right: 0; margin-bottom: 15px; padding: 15px; box-sizing: border-box; text-align: center; }
.errormessagebox p { margin: 0; }

Now once you get yours setup you will have to add the keys for each product (same naming conventions), the value for each item can be whatever you want the batch size to be. 

That's it. Thanks to VGC Unlimited for getting me 95% of the way there, and for sharing it with the Shopify community! 

WOTIO App: https://apps.shopify.com/automatic-account-invites
1 Like
Highlighted
New Member
1 0 0

Thanks for the detailed instructions.  Once you were able to get the code working to enforce Min. Order Quantity (MoQ) for each item, do you have a way to upload the MoQ for each product in a .csv?  Setting moq's for thousands of items is not workable obviously.

0 Likes
Highlighted
Shopify Partner
2 0 0

Can you provide a screenshot of what this looks like all setup? Do the error message of not having fullfiled minimum qty requirements happen before they add to cart or before they checkout? Can this solution automatically set the qty box on the product page to a number and not allow that number to go below?

0 Likes
Highlighted
Excursionist
30 0 5

Apparently they have a paid version that allows for bulk CSV imports: http://metafieldspro.webifytechnology.com/

WOTIO App: https://apps.shopify.com/automatic-account-invites
0 Likes
Highlighted
Excursionist
30 0 5

@Zoe Zhou - I have not put any logic into my product details page, but I suppose there would be a way to do that. 

Here is what it looks like in the cart when the minimum order QTY isn' t met.



 

WOTIO App: https://apps.shopify.com/automatic-account-invites
1 Like
Highlighted
Shopify Partner
2 0 0

That looks pretty good! Thank you for the screenshot and quick response. How does this deal with variants? Can the minimum qty be shared across multiple variants of the same product? eg: Custom T-Shirt min qty = 24 ... can they buy 8 blue ones, 8 red ones, 8 yellow ones to fullfil the 24 min qty rule?

0 Likes
Highlighted
Excursionist
30 0 5

Yes - it takes into account any variant combination. You can see it in action on our site with this product: https://deeko.com/collections/esports/products/ohio-esports-association-team-jersey

WOTIO App: https://apps.shopify.com/automatic-account-invites
1 Like
New Member
1 0 0

I was trying to figure out a solution for this too. With your solution you can still get around it by adding 4 to the cart initially and then on the cart page hitting the - button on the qty selector to bring it to 1 and then clicking checkout.. Although most customers probably won't do that.

Not sure why shopify doesn't require you to click update cart first if you change the qty. 

I found this post helpful for hiding the checkout button if they change the quantity on the cart page forcing them to do a cart update first before they continue to checkout. 

https://ecommerce.shopify.com/c/shopify-discussion/t/if-quantity-selected-x-show-message-326791

Thanks for sharing. 

0 Likes
Highlighted
Shopify Partner
10 0 0

Just wanted to say thank you for positing this solution Brian!

My site uses the Showtime theme which automatically updates the cart using Ajax and does not come with an 'update cart' button. To help get around the issue that Jeremy described I added these pieces to my code.

<!-- To Hide the Checkout button on every page re-load -->
<button type="submit" id="checkoutBtn" name="checkout" value="{{ 'cart.general.checkout' | t }}" class="next_btn">{{ 'cart.general.checkout' | t }}</button>
{% endunless %}

<!-- An Update Cart button to reload the Page -->
<button type="submit" class="next_btn" style="background-color:#fe7e29;margin-right:20px" value="Update quantities">Update Cart</button>

<!-- Hide PayPal or any other payment method buttons -->
{% unless batchesok == false %}
{% if additional_checkout_buttons %}
<div id="addCheckoutBtn" class="additional-checkout">
{{ content_for_additional_checkout_buttons }}</div>
{% endif %}
{% endunless %}

Also simple jQuery to the add/subtract quantity buttons. The for loop would not let me select Ids so I added classes to each.

<script>
$(".plus_btn, .minus_btn, .remove_item_button").click(function(){
$("#checkoutBtn").css("display", "none");
$("#addCheckoutBtn").css("display", "none");
});
</script>

 

May not be the most efficient way of going about this and I appreciate any feedback this if there is any.

0 Likes
Highlighted
New Member
2 0 4

I'm upvoting this becuase this is a programming element that should be apart of the website design with a purchase of a shopify website.  This is elemental and basic enough need not to have to learn code to be able to implement.  It SHOULD BE A KEY STROKE OPTION.  What's the point of a user friendly website builder if you have to learn code to get a basic feature (OR PAY $5/mo extra for it from an outside source?).

I appreciate all the help in the forums, but every example I've tried I get formatting issues and the end result has a pay button that doesn't work.

Come on Shopify, this is enough to make me drop the product ALTOGETHOR!

New User

4 Likes