Personalized checkout and custom promotions with Shopify Scripts
Hi,
I want to be able to set a threshold on products that have multiple variants so that any variant with a stock level of three or less is treated as sold out. I’ve gotten some help along the way and the function now works: variants with low stock levels are greyed out and unselectable.
However, another bug has cropped up: the wrong variant ends up in the cart. For example, if you choose to buy the “30 ml” variant on this example , the first available variant is added to the cart instead (in this case, 100 ml). We can’t find any solution at all. Does anyone have any ideas?
The theme is Be Yours, and the modified files are product-variant-options.liquid and product-variant-picker.liquid. Thanks!
Solved! Go to the solution
This is an accepted solution.
I approached it differently. Instead of disabling the variant buttons for items with low stock, I chose to hide them. That way, the add-to-cart functionality etc. wasn’t affected. So case closed.
Hi Markit-Themes,
Thanks a lot for stopping by.
Here is the modified product-variant-options.liquid
{% comment %}
Renders product variant options
Accepts:
- product: {Object} product object.
- option: {Object} current product_option object.
- block: {Object} block object.
- picker_type: {String} type of picker to display
Usage:
{% render 'product-variant-options',
product: product,
option: option,
block: block,
picker_type: picker_type
%}
{% endcomment %}
{%- liquid
assign product_form_id = 'product-form-' | append: section.id
assign has_set_default = false
-%}
{%- for value in option.values -%}
{%- liquid
# --- SWATCH / DROPDOWN-LOGIK (oförändrad) ---
assign swatch_focal_point = null
assign swatch_value = value | split: ' ' | last | handle
if block.settings.swatch_source == 'native'
if value.swatch.image
assign image_url = value.swatch.image | image_url: width: 50
assign swatch_value = 'url(' | append: image_url | append: ')'
assign swatch_focal_point = value.swatch.image.presentation.focal_point
elsif value.swatch.color
assign swatch_value = 'rgb(' | append: value.swatch.color.rgb | append: ')'
else
assign swatch_value = null
endif
else
if block.settings.swatch_source == 'variant'
if value.variant.image
assign color_variant_image = value.variant.image | image_url: width: 50
assign swatch_value = 'url(' | append: color_variant_image | append: ')'
endif
else
# (din befintliga “custom swatch”-logik här…)
endif
endif
# --- NY STOCK-LOGIK ---
assign this_variant = value.variant
assign stock = this_variant.inventory_quantity
assign option_disabled = true
if stock > 3
assign option_disabled = false
endif
-%}
{%- comment -%} Sätt “checked” på första tillgängliga variant {%- endcomment -%}
{% assign is_checked = false %}
{% if option_disabled == false %}
{% if value.selected or has_set_default == false %}
{% assign is_checked = true %}
{% assign has_set_default = true %}
{% endif %}
{% endif %}
{%- capture input_id -%}{{ section.id }}-{{ option.position }}-{{ forloop.index0 }}{%- endcapture -%}
{%- capture input_name -%}{{ option.name }}-{{ option.position }}{%- endcapture -%}
{%- capture input_dataset -%}
data-product-url="{{ value.product_url }}"
data-option-value-id="{{ value.id }}"
{%- endcapture -%}
{%- capture label_unavailable -%}
<span class="visually-hidden label-unavailable">
{{ 'products.product.variant_sold_out_or_unavailable' | t }}
</span>
{%- endcapture -%}
{%- if picker_type == 'swatch' -%}
{%- capture help_text -%}
<span class="visually-hidden">{{ value | escape }}</span>
{{ label_unavailable }}
{%- endcapture -%}
{% render 'swatch-input',
id: input_id,
name: input_name,
value: value | escape,
swatch: value.swatch,
product_form_id: product_form_id,
checked: is_checked,
visually_disabled: option_disabled,
shape: block.settings.swatch_type,
source: block.settings.swatch_source,
option: option,
product: product,
help_text: help_text,
additional_props: input_dataset
%}
{%- elsif picker_type == 'button' -%}
<input
type="radio"
id="{{ input_id }}"
name="{{ input_name | escape }}"
value="{{ value | escape }}"
form="{{ product_form_id }}"
class="button-input__input{% if option_disabled %} disabled{% endif %}"
{% if option_disabled %}disabled aria-disabled="true"{% endif %}
{% if is_checked %}checked{% endif %}
{{ input_dataset }}
>
<label for="{{ input_id }}" class="{% if option_disabled %}is-sold-out{% endif %}">
{{ value }}
</label>
{%- elsif picker_type == 'dropdown' or picker_type == 'swatch_dropdown' -%}
<option
id="{{ input_id }}"
value="{{ value | escape }}"
{% if is_checked %}selected{% endif %}
{% if swatch_value and picker_type == 'swatch_dropdown' %}
data-option-swatch-value="{{ swatch_value }}"
{% if swatch_focal_point %}
data-option-swatch-focal-point="{{ swatch_focal_point }}"
{% endif %}
{% endif %}
{{ input_dataset }}
>
{% if option_disabled %}
{{ 'products.product.value_unavailable' | t: option_value: value }}
{% else %}
{{ value }}
{% endif %}
</option>
{%- endif -%}
{%- endfor -%}
Here is the modified product-variant-picker.liquid
{% comment %}
Renders product variant-picker
Accepts:
- product: {Object} product object.
- block: {Object} passing the block information.
- product_form_id: {String} Id of the product form to which the variant picker is associated.
Usage:
{% render 'product-variant-picker', product: product, block: block, product_form_id: product_form_id %}
{% endcomment %}
{%- unless product.has_only_default_variant -%}
<variant-selects
data-primary
id="variant-selects-{{ section.id }}"
data-section="{{ section.id }}"
{{ block.shopify_attributes }}
>
{%- for option in product.options_with_values -%}
{%- liquid
assign picker_type = block.settings.picker_type
if block.settings.swatch_type != 'none'
assign native_swatch_eligible = false
assign custom_swatch_eligible = false
if block.settings.swatch_source == 'native'
assign native_swatch_count = option.values | map: 'swatch' | compact | size
if native_swatch_count > 0
assign native_swatch_eligible = true
endif
else
assign custom_swatch_trigger = 'products.product.color_swatch_trigger' | t | downcase
assign downcased_option = option.name | downcase
if custom_swatch_trigger contains downcased_option
assign custom_swatch_eligible = true
endif
endif
if native_swatch_eligible or custom_swatch_eligible
if block.settings.picker_type == 'dropdown'
assign picker_type = 'swatch_dropdown'
else
assign picker_type = 'swatch'
endif
endif
endif
assign is_size = false
if block.settings.size_chart != blank
assign size_trigger = 'products.product.size_chart_trigger' | t | downcase
assign downcased_option = option.name | downcase
if size_trigger contains downcased_option
assign is_size = true
endif
endif
-%}
{%- capture sizechart_cta -%}
{%- if is_size -%}
<div class="form__popup" id="size-{{ section.id }}">
<modal-opener
class="no-js-hidden"
data-modal="#PopupModal-{{ block.id }}"
{{ block.shopify_attributes }}
>
<button
id="ProductPopup-{{ block.id }}"
class="link link-with-icon"
type="button"
aria-haspopup="dialog"
>
{% render 'icon', icon: 'ruler' %}
<span class="label">
{{ 'products.product.size_chart' | t | default: block.settings.size_chart.title }}
</span>
</button>
</modal-opener>
<a href="{{ block.settings.size_chart.url }}" class="link link-with-icon no-js">
{% render 'icon', icon: 'ruler' %}
<span class="label">
{{ 'products.product.size_chart' | t | default: block.settings.size_chart.title }}
</span>
</a>
</div>
{%- endif -%}
{%- endcapture -%}
{%- if picker_type == 'swatch' -%}
<fieldset class="js product-form__input product-form__input--swatch">
{%- if block.settings.show_variant_labels -%}
<legend class="form__label">
{{ option.name }}:
<span data-selected-value>{{ option.selected_value }}</span>
</legend>
{%- endif -%}
{% render 'product-variant-options',
product: product,
option: option,
block: block,
picker_type: picker_type
%}
{{- sizechart_cta -}}
</fieldset>
{%- elsif picker_type == 'button' -%}
<fieldset class="js product-form__input product-form__input--pill">
{%- if block.settings.show_variant_labels -%}
<legend class="form__label">{{ option.name }}</legend>
{%- endif -%}
{% render 'product-variant-options',
product: product,
option: option,
block: block,
picker_type: picker_type
%}
{{- sizechart_cta -}}
</fieldset>
{%- else -%}
<div class="product-form__input product-form__input--dropdown">
{%- if block.settings.show_variant_labels -%}
<label
class="form__label"
for="Option-{{ section.id }}-{{ forloop.index0 }}"
>
{{ option.name }}
</label>
{%- endif -%}
<div class="select">
{%- if picker_type == 'swatch_dropdown' -%}
<span data-selected-value class="dropdown-swatch">
{% if block.settings.swatch_source == 'native' %}
{% render 'swatch',
swatch: option.selected_value.swatch,
shape: block.settings.swatch_type
%}
{% else %}
{% render 'swatch-custom',
product: product,
option: option,
source: block.settings.swatch_source,
shape: block.settings.swatch_type
%}
{% endif %}
</span>
{%- endif -%}
<select
id="Option-{{ section.id }}-{{ forloop.index0 }}"
class="select__select"
name="options[{{ option.name | escape }}]"
form="{{ product_form_id }}"
>
{% render 'product-variant-options',
product: product,
option: option,
block: block,
picker_type: picker_type
%}
</select>
<span class="svg-wrapper">
{{- 'icon-caret.svg' | inline_asset_content -}}
</span>
</div>
{{- sizechart_cta -}}
</div>
{%- endif -%}
{%- endfor -%}
{%- comment -%}
Välj först en variant som har mer än 3 i lager.
Om ingen sådan finns, fallback till Shopify’s selected_or_first_available_variant.
{%- endcomment -%}
{%- assign fallback_variant = product.selected_or_first_available_variant -%}
{%- assign chosen_variant = nil -%}
{%- for variant in product.variants -%}
{% if variant.inventory_quantity > 3 %}
{%- assign chosen_variant = variant -%}
{%- break -%}
{% endif %}
{%- endfor -%}
{%- if chosen_variant == nil -%}
{%- assign chosen_variant = fallback_variant -%}
{%- endif -%}
<script type="application/json" data-selected-variant>
{{ chosen_variant | json }}
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var vs = document.querySelector('#variant-selects-{{ section.id }}');
if (!vs) return;
var checkedInput = vs.querySelector('input[type="radio"]:checked');
if (checkedInput) {
checkedInput.dispatchEvent(new Event('change', { bubbles: true }));
}
});
</script>
</variant-selects>
{%- endunless -%}
Here is the original product-variant-options.liquid
{% comment %}
Renders product variant options
Accepts:
- product: {Object} product object.
- option: {Object} current product_option object.
- block: {Object} block object.
- picker_type: {String} type of picker to dispay
Usage:
{% render 'product-variant-options',
product: product,
option: option,
block: block
picker_type: picker_type
%}
{% endcomment %}
{%- liquid
assign product_form_id = 'product-form-' | append: section.id
-%}
{%- for value in option.values -%}
{%- liquid
assign swatch_focal_point = null
assign swatch_value = value | split: ' ' | last | handle
comment
fall-back value ↑ or null?
endcomment
if block.settings.swatch_source == 'native'
if value.swatch.image
assign image_url = value.swatch.image | image_url: width: 50
assign swatch_value = 'url(' | append: image_url | append: ')'
assign swatch_focal_point = value.swatch.image.presentation.focal_point
elsif value.swatch.color
assign swatch_value = 'rgb(' | append: value.swatch.color.rgb | append: ')'
else
assign swatch_value = null
endif
else
if block.settings.swatch_source == 'variant'
if value.variant.image
assign color_variant_image = value.variant.image | image_url: width: 50
assign swatch_value = 'url(' | append: color_variant_image | append: ')'
endif
else
assign swatch_file_extension = 'png'
assign file_name_uniq = product.id | append: '_' | append: value | handle | append: '.' | append: swatch_file_extension
assign file_name_custom = blank
assign file_name_alt = value | handle | append: '.' | append: swatch_file_extension
assign value_downcase = value | downcase
assign swatch_config = settings.swatch_config | newline_to_br | split: '<br />'
for swatch in swatch_config
assign swatch_parts = swatch | strip | split: ':'
assign swatch_name = swatch_parts.first | downcase | strip
if swatch_name == value_downcase
assign swatch_entry = swatch_parts.last | strip
if swatch_entry contains '#'
assign swatch_value = swatch_entry
assign file_name_alt = blank
else
assign file_name_custom = swatch_entry
endif
break
endif
endfor
assign file_name_final = blank
if images[file_name_uniq] != blank
assign file_name_final = file_name_uniq
elsif file_name_custom != blank and images[file_name_custom] != blank
assign file_name_final = file_name_custom
elsif images[file_name_alt] != blank
assign file_name_final = file_name_alt
endif
assign swatch_image = blank
if images[file_name_final] != blank
assign swatch_image = images[file_name_final] | image_url: width: 50
elsif file_name_final contains '//cdn.shopify.com/'
assign swatch_image = file_name_final
endif
if swatch_image != blank
assign swatch_value = 'url(' | append: swatch_image | append: ')'
endif
endif
endif
assign option_disabled = true
if value.available
assign option_disabled = false
endif
-%}
{%- capture input_id -%}
{{ section.id }}-{{ option.position }}-{{ forloop.index0 -}}
{%- endcapture -%}
{%- capture input_name -%}
{{ option.name }}-{{ option.position }}
{%- endcapture -%}
{%- capture input_dataset -%}
data-product-url="{{ value.product_url }}"
data-option-value-id="{{ value.id }}"
{% if option_disabled %}data-crossout{% unless block.settings.enable_swatch_unavailable_click %} disabled{% endunless %}{% endif %}
{%- endcapture -%}
{%- capture label_unavailable -%}
<span class="visually-hidden label-unavailable">
{{- 'products.product.variant_sold_out_or_unavailable' | t -}}
</span>
{%- endcapture -%}
{%- if picker_type == 'swatch' -%}
{%- capture help_text -%}
<span class="visually-hidden">{{ value | escape }}</span>
{{ label_unavailable }}
{%- endcapture -%}
{%
render 'swatch-input',
id: input_id,
name: input_name,
value: value | escape,
swatch: value.swatch,
product_form_id: product_form_id,
checked: value.selected,
visually_disabled: option_disabled,
shape: block.settings.swatch_type,
source: block.settings.swatch_source,
option: option,
product: product,
help_text: help_text,
additional_props: input_dataset
%}
{%- elsif picker_type == 'button' -%}
<input
type="radio"
id="{{ input_id }}"
name="{{ input_name | escape }}"
value="{{ value | escape }}"
form="{{ product_form_id }}"
{% if value.selected %}
checked
{% endif %}
class="button-input__input{% if option_disabled %} disabled{% endif %}"
{{ input_dataset }}
>
<label for="{{ input_id }}">
{{ value -}}
{{ label_unavailable }}
</label>
{%- elsif picker_type == 'dropdown' or picker_type == 'swatch_dropdown' -%}
<option
id="{{ input_id }}"
value="{{ value | escape }}"
{% if value.selected %}
selected="selected"
{% endif %}
{% if swatch_value and picker_type == 'swatch_dropdown' %}
data-option-swatch-value="{{ swatch_value }}"
{% if swatch_focal_point %}
data-option-swatch-focal-point="{{ swatch_focal_point }}"
{% endif %}
{% endif %}
{{ input_dataset }}
>
{% if option_disabled -%}
{{- 'products.product.value_unavailable' | t: option_value: value -}}
{%- else -%}
{{- value -}}
{%- endif %}
</option>
{%- endif -%}
{%- endfor -%}
Here comes the original product-variant-picker.liquid
{% comment %}
Renders product variant-picker
Accepts:
- product: {Object} product object.
- block: {Object} passing the block information.
- product_form_id: {String} Id of the product form to which the variant picker is associated.
Usage:
{% render 'product-variant-picker', product: product, block: block, product_form_id: product_form_id %}
{% endcomment %}
{%- unless product.has_only_default_variant -%}
<variant-selects data-primary
id="variant-selects-{{ section.id }}"
data-section="{{ section.id }}"
{{ block.shopify_attributes }}
>
{%- for option in product.options_with_values -%}
{%- liquid
assign picker_type = block.settings.picker_type
if block.settings.swatch_type != 'none'
assign native_swatch_eligible = false
assign custom_swatch_eligible = false
if block.settings.swatch_source == 'native'
assign native_swatch_count = option.values | map: 'swatch' | compact | size
if native_swatch_count > 0
assign native_swatch_eligible = true
endif
else
assign custom_swatch_trigger = 'products.product.color_swatch_trigger' | t | downcase
assign downcased_option = option.name | downcase
if custom_swatch_trigger contains downcased_option
assign custom_swatch_eligible = true
endif
endif
if native_swatch_eligible == true or custom_swatch_eligible == true
if block.settings.picker_type == 'dropdown'
assign picker_type = 'swatch_dropdown'
else
assign picker_type = 'swatch'
endif
endif
endif
assign is_size = false
if block.settings.size_chart != blank
assign size_trigger = 'products.product.size_chart_trigger' | t | downcase
assign downcased_option = option.name | downcase
if size_trigger contains downcased_option
assign is_size = true
endif
endif
-%}
{%- capture sizechart_cta -%}
{%- if is_size -%}
<div class="form__popup" id="size-{{ section.id }}">
<modal-opener class="no-js-hidden" data-modal="#PopupModal-{{ block.id }}" {{ block.shopify_attributes }}>
<button id="ProductPopup-{{ block.id }}" class="link link-with-icon" type="button" aria-haspopup="dialog">
{% render 'icon', icon: 'ruler' %}
<span class="label">{{ 'products.product.size_chart' | t | default: block.settings.size_chart.title }}</span>
</button>
</modal-opener>
<a href="{{ block.settings.size_chart.url }}" class="link link-with-icon no-js">
{% render 'icon', icon: 'ruler' %}
<span class="label">{{ 'products.product.size_chart' | t | default: block.settings.size_chart.title }}</span>
</a>
</div>
{%- endif -%}
{%- endcapture -%}
{%- if picker_type == 'swatch' -%}
<fieldset class="js product-form__input product-form__input--swatch">
{%- if block.settings.show_variant_labels -%}
<legend class="form__label">
{{ option.name }}:
<span data-selected-value>
{{- option.selected_value -}}
</span>
</legend>
{%- endif -%}
{% render 'product-variant-options',
product: product,
option: option,
block: block,
picker_type: picker_type
%}
{{- sizechart_cta -}}
</fieldset>
{%- elsif picker_type == 'button' -%}
<fieldset class="js product-form__input product-form__input--pill">
{%- if block.settings.show_variant_labels -%}
<legend class="form__label">{{ option.name }}</legend>
{%- endif -%}
{% render 'product-variant-options',
product: product,
option: option,
block: block,
picker_type: picker_type
%}
{{- sizechart_cta -}}
</fieldset>
{%- else -%}
<div class="product-form__input product-form__input--dropdown">
{%- if block.settings.show_variant_labels -%}
<label class="form__label" for="Option-{{ section.id }}-{{ forloop.index0 }}">
{{ option.name }}
</label>
{%- endif -%}
<div class="select">
{%- if picker_type == 'swatch_dropdown' -%}
<span
data-selected-value
class="dropdown-swatch"
>
{% if block.settings.swatch_source == 'native' %}
{% render 'swatch', swatch: option.selected_value.swatch, shape: block.settings.swatch_type %}
{% else %}
{% render 'swatch-custom', product: product, option: option, source: block.settings.swatch_source, shape: block.settings.swatch_type %}
{% endif %}
</span>
{%- endif -%}
<select
id="Option-{{ section.id }}-{{ forloop.index0 }}"
class="select__select"
name="options[{{ option.name | escape }}]"
form="{{ product_form_id }}"
>
{% render 'product-variant-options',
product: product,
option: option,
block: block,
picker_type: picker_type
%}
</select>
<span class="svg-wrapper">
{{- 'icon-caret.svg' | inline_asset_content -}}
</span>
</div>
{{- sizechart_cta -}}
</div>
{%- endif -%}
{%- endfor -%}
<script type="application/json" data-selected-variant>
{{ product.selected_or_first_available_variant | json }}
</script>
</variant-selects>
{%- endunless -%}
Could you please share your Store URL and password so that I take a look and provide you solution code.
Thanks
Hi ScriptFlow! The store is open for now and here is URLto example product with variants https://doftnoter.se/products/parfym-damer-jil-sander-jil-sander-edp-n%C2%BA-4?variant=5063743805883...
This is an accepted solution.
I approached it differently. Instead of disabling the variant buttons for items with low stock, I chose to hide them. That way, the add-to-cart functionality etc. wasn’t affected. So case closed.
June brought summer energy to our community. Members jumped in with solutions, clicked ...
By JasonH Jun 5, 2025Learn how to build powerful custom workflows in Shopify Flow with expert guidance from ...
By Jacqui May 7, 2025Did You Know? May is named after Maia, the Roman goddess of growth and flourishing! ...
By JasonH May 2, 2025