Direct answer
On a Shopify product template, read the initial purchasable variant from product.selected_or_first_available_variant. Shopify selects the variant from the ?variant=ID URL parameter when it is present; otherwise it returns the first available variant, or the first variant if none are available. Read SKU, price, availability, barcode, options, and the variant-specific URL from that variant object—not from one shared product field.
The Liquid below produces the initial server-rendered facts. Your theme’s variant-picker JavaScript must update the same visible facts after a shopper changes an option. Shopify documents both product.selected_or_first_available_variant and the available variant properties.
What you will verify
You will distinguish product-level facts from sellable variant facts and render a small inspectable fact block that stays synchronized with the selected variant.
Rhode Glazing Milk illustrates the requirement: 4.2 oz and 1.7 oz sizes had separate $32 and $20 offers. Treating the first offer as the whole product would answer a size-specific question incorrectly.
Product facts versus variant facts
| Product-level | Variant-level |
|---|---|
| title | SKU |
| vendor or brand context | barcode |
| description | option values |
| product type and category | price and compare-at price |
| shared ingredients | availability |
| shared compatibility | inventory-linked state |
A bundle may need a third layer for included items.
Exact Shopify Admin path
Online Store → Themes → duplicate live theme → Edit code
Open the main product section used by the product template. In current theme structures this is often under sections/, but the exact filename varies.
Resolve the current variant
{% assign current_variant = product.selected_or_first_available_variant %}
{% if current_variant %}
<dl class="product-facts" data-product-id="{{ product.id }}">
<div>
<dt>Product</dt>
<dd>{{ product.title | escape }}</dd>
</div>
<div>
<dt>Variant</dt>
<dd>
{% if current_variant.title == 'Default Title' %}
Standard
{% else %}
{{ current_variant.title | escape }}
{% endif %}
</dd>
</div>
{% if current_variant.sku != blank %}
<div>
<dt>SKU</dt>
<dd>{{ current_variant.sku | escape }}</dd>
</div>
{% endif %}
<div>
<dt>Price</dt>
<dd>{{ current_variant.price | money_with_currency }}</dd>
</div>
<div>
<dt>Availability</dt>
<dd>{% if current_variant.available %}In stock{% else %}Sold out{% endif %}</dd>
</div>
<div>
<dt>Variant link</dt>
<dd><a href="{{ current_variant.url }}">View this option</a></dd>
</div>
</dl>
{% else %}
<p>This option combination is unavailable.</p>
{% endif %}
This is the initial server-rendered state. The guard handles an unavailable option-value combination in themes that support high-variant products. A variant picker must update the same visible values after selection. Do not replace the theme’s existing picker logic without testing subscriptions, selling plans, quantity rules, and add-to-cart behavior.
Expose a controlled variant payload
Do not serialize the entire product object into arbitrary attributes. Emit only fields the product UI needs.
<script type="application/json" data-product-variants>
[
{% for variant in product.variants %}
{
"id": {{ variant.id | json }},
"title": {{ variant.title | json }},
"sku": {{ variant.sku | json }},
"available": {{ variant.available | json }},
"price": {{ variant.price | json }},
"url": {{ variant.url | json }},
"options": {{ variant.options | json }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
</script>
Use a theme component to update price, SKU, availability, the recoverable variant URL, and add-to-cart controls from one selected variant. Do not automatically make every ?variant= URL canonical: keep the theme’s existing canonical strategy unless a deliberate duplicate-URL review supports changing it.
How to deep-link a Shopify variant
Use the variant object’s url value when you render a link, or add the variant ID as the product URL’s variant parameter. A recoverable URL lets a buyer or catalog record return to the same selection:
<a href="{{ current_variant.url }}">
{{ product.title | escape }} — {{ current_variant.title | escape }}
</a>
Shopify’s theme guidance for product variants explains deep-linked selection and option availability. The link preserves selection; it does not by itself keep visible price, stock, or the buy button synchronized after an in-page option change.
Real failure patterns
Shared SKU across sizes
The observed Rhode entities used one SKU for two sizes. If Shopify variant records have unique SKUs, ensure the rendered graph reads variant.sku, not a product metafield copied to every variant.
Default Title in public name
The observed HexClad entity exposed Default Title. Suppress that label for a simple product without pretending that all products lack variants.
Offer range versus selected price
Puffy’s Product graph exposed a $449–$1,749 range. That supports discovery, but the selected Queen variant still needs its exact current price when the buyer asks for Queen.
Expected result
Change variants and confirm:
- option label changes
- SKU changes where expected
- price and currency change correctly
- availability and buy button agree
- URL or variant parameter remains recoverable
- structured offer is not stuck on the first variant
Verification
Test with JavaScript enabled and disabled. Save initial HTML for direct retrieval, then inspect the rendered DOM after switching variants. Check at least one in-stock variant, one sold-out variant, and a direct ?variant=ID visit. If you are also fixing the page’s product markup, continue with inspect and fix Shopify Product JSON-LD and compare the selected offer with the visible facts.
Rollback
Keep changes in a duplicate theme. Restore the previous product section if variant picker, analytics, subscriptions, or add-to-cart behavior changes unexpectedly.
Community discussion
Add to the article
Ask a technical question, share a storefront result, or challenge a conclusion with evidence.