I have a client who has a large selection of products that have two different size options, width and size. Their products have up to 30 size options and can have up to four width options. They hit the 100 variant limit on Shopify.
There are a number of solutions to this, none of them ideal. When something doesn’t fit a system the first thing to consider is the product model. The products could be split into ‘big’ and ‘small’ items but this would be at the expense of user experience. A third party plugin such as Product Variants Reloaded could be used, but these add extra cost and add a dependency to the inventory, which is a high risk. The cost of a plugin can end up being more than the monthly subscription.
I realised that none of the products varied in price as a result of size; so I could remove sizes from being variants at all. I implemented them as line item properties. If you are not familiar with line item properties, they allow you to add extra fields to the checkout form on the product page. This information gets passed on with the order and can be displayed in the cart and checkout process.
All I needed to do was to somehow construct the size dropdown with the correct options for the product on the product page and then pass the selection on with the Add to Cart action.
The two choices for storing the size option data against a product are metafields and tags: both have their advantages and disadvantages.
Metafields feel like the right solution as they are designed for storing custom data against products. Unfortunately they are not available for editing on the product pages in the backend. There are plugins (which I am trying to avoid) and there is a hack which uses the bulk product editor, but I want to provide my client with a simple interface to work with.
Tags can provide a simple editing experience but at the expense of being slightly clunky in a Shopify kind of way. They will however pollute the tag cloud with slightly cryptic labels. My client was not using the tags directly, and I have made other similar uses of them before, so I went with a fairly simple scheme…
If a product needs to display size options then a tag with the prefix SIZE: was added to the product. After the prefix there follows a list of dash separated sizes in millimetres, for example SIZE:50-52-56. Since there were many products that shared the same two set of sizes, tags were designated for these groups, for example SIZE:standard2 is the equivalent of SIZE:50-52-54-56-58-60-62-64-66-68 (this is a common combination). This was done to reduce errors and cover the majority of the products with easier to read tags.
The final code checks to see if there is a SIZE: tag and then iterates though the list of numbers (or the numbers in a standard set) and builds the options. The text for the options is not the raw number in the tags, this is used to look up the text for the option; so for example 20 is translated into 20mm. This is because many of the size options will have additional text for other markets such as the USA.
I added a render tag and the various lists and parameters in order for it to build the select list:
{% assign tag_prefix = 'SIZE:' %}
{% assign option_label = 'Size' %}
{% assign first_size = 48 %}
{% assign size_info_text = '48mm|49mm|50mm|51mm|52mm|53mm|54mm|55mm|56mm|57mm...' %}
{% assign standard_sizes_1 = '48-49-50-51-52-53-54-55-56-57-58-59-60-61-62-63-64-65-66-67' %}
{% assign standard_sizes_2 = '50-52-54-56-58-60-62-64-66-68' %}
{% render 'dp-sizes', tag_prefix: tag_prefix, option_label: option_label, first_option: first_size, option_info_text:size_info_text, standard_options_1:standard_sizes_1,standard_options_2:standard_sizes_2 %}
And created a snippet to render an option list from this data:
{% assign option_info = option_info_text | split: "|" %}
{% for current_tag in product.tags %}
{% if current_tag contains tag_prefix %}
{% assign option_list = current_tag | replace: tag_prefix, '' %}
{% if option_list == 'standard1' %}
{% assign option_list = standard_options_1 %}
{% elsif option_list == 'standard2' %}
{% assign option_list = standard_options_2 %}
{% endif %}
{% assign option_array = option_list | split: "-" %}
<div class="dp-half-width-select">
<p class="line-item-property__field">
<label>{{option_label}}</label>
<select id="option" name="properties[option]">
{% for option in option_array %}
{% assign option_index = option | minus: first_option %}
<option value="{{ option }}">{{ option_info[option_index] }}</option>
{% endfor %}
</select>
</p>
</div>
{% break %}
{% endif %}
{% endfor %}
This solution was made for two options both numeric values which allowed me to take a shortcut to get around the lack of associative arrays in liquid (the minus {{first_option}} ‘hack’ on lines 17 & 18). While you could use the numbers for something like colours (e.g. 1 = red, 2 = blue etc.) the code would be best refactored but that will be for another day. Another limitation of this solution is that it cannot be used on products and variants if they do not have these options available to all variants as there is no way to control invalid combinations. I wrote this code for a single site but I know this will get reused in the near future on something else.
This is one of those occasions when I am a little amazed by a limitation of Shopify; having a maximum 100 variant limit is quite constraining!
.