Skip to content

Reading swatch data in Liquid

Read swatch data from metafields to render swatches your own way. The metafields expose the same data the default widgets use, so you can build custom layouts, alternative markup, or theme-specific styling without losing parity.

Product groups live on each product. Variant swatch colors live on the shop. Payload fields are documented under public metafields.

{%- assign groups = product.metafields.pl_swatches.groups.value -%}
{%- if groups -%}
{%- for group in groups -%}
<div class="my-swatch-group">
<span class="label">{{ group.option_name }}</span>
<ul>
{%- for swatch in group.swatches -%}
<li>
<a href="{{ product.url | replace: product.handle, swatch.handle }}">
{{ swatch.name }}
</a>
</li>
{%- endfor -%}
</ul>
</div>
{%- endfor -%}
{%- endif -%}

groups is an array because a product can belong to several groups at once (for example, a Color group and a Size group). Each group carries its own option_name and swatches list.

Each entry in group.swatches has these fields:

FieldWhat it is
handleShopify handle of the linked product
nameDisplay name (e.g. “Black”, “Navy”)
typeone_color, two_colors, custom_image, product_image, image_with_text, or pill
color_oneHex color (when type is one_color or two_colors)
color_twoSecond hex color (when type is two_colors)
imageImage URL (when type is custom_image or image_with_text)
out_of_stocktrue or false

The active swatch is the one whose handle matches the current product:

{%- for swatch in group.swatches -%}
{%- if swatch.handle == product.handle -%}
<span class="current">{{ swatch.name }}</span>
{%- else -%}
<a href="{{ product.url | replace: product.handle, swatch.handle }}">
{{ swatch.name }}
</a>
{%- endif -%}
{%- endfor -%}

Variant colors come from shop.metafields.pl_swatches.options. Loop the product’s options and look up each name in that object.

{%- assign options_data = shop.metafields.pl_swatches.options.value -%}
{%- assign name_lookup = options_data._name_lookup -%}
{%- for option in product.options_with_values -%}
{%- assign option_name = option.name | strip -%}
{%- assign option_name_downcased = option_name | downcase -%}
{%- assign canonical_option_name = name_lookup[option_name_downcased] | default: option_name -%}
{%- assign option_config = options_data[canonical_option_name] -%}
{%- if option_config == blank -%}
{%- continue -%}
{%- endif -%}
{%- assign value_lookup = option_config._lookup -%}
<span class="label">{{ option.name }}</span>
{%- for value in option.values -%}
{%- assign canonical_value = value_lookup[value] | default: value -%}
{%- assign swatch = option_config.values[canonical_value] -%}
{%- if swatch.color_one -%}
<span aria-hidden="true" style="display: inline-block; width: 1.5rem; height: 1.5rem; background: {{ swatch.color_one }};"></span>
{%- endif -%}
{{ value }}
{%- endfor -%}
{%- endfor -%}

The shop object includes every option in the store. Skip names that are not on this product. See public metafields for the other fields (image, color_two, swatch_style) and how translated names are keyed.