When a store runs a free gift discount, the cart tells your theme about every gift the shopper has unlocked in cart.gift_offers, and marks a gift in the cart with line.is_free_gift. Show both in the cart drawer and on the cart page; a shopper who unlocks a gift your theme does not show never sees it. Read them from any section that declares cart in its ctx_needs.
Two kinds of gift#
offer: the shopper has unlocked the gift, but it is not in the cart yet. Show it and let the shopper add it.auto_add: the store adds the gift to the cart by itself. It appears as a cart line withline.is_free_gift; there is nothing to claim.
Fields#
Field |
Type |
Meaning |
|---|---|---|
|
String |
The discount that grants the gift. |
|
String |
The discount's name, to show beside the gift. |
|
String |
|
|
Integer |
The gift product. |
|
Integer or null |
The exact variant, or null when the gift is a whole product with no variants. |
|
Integer |
How many of the gift the cart may hold. |
|
Integer |
How many are already in the cart. |
|
Integer |
How many the shopper can still add. |
|
Object or null |
|
|
Object or null |
|
Gift offers are worked out again on every cart change, so an offer disappears from cart.gift_offers as soon as the cart stops qualifying or the gift is fully claimed. The full list of fields is in the Context Reference under Cart, wishlist, and reviews.
Show an unlocked gift#
Show an offer when its mode is offer, remaining_quantity is above zero and product is not null. Adding the gift is an ordinary cart_add form: send the product, the variant when there is one, and remaining_quantity. The store accepts it only while the cart qualifies, and your theme's usual add-to-cart handling refreshes the drawer.
{%- for gift in cart.gift_offers -%}
{%- if gift.mode == 'offer' and gift.remaining_quantity > 0 and gift.product -%}
{%- assign gift_media = gift.variant.media | default: gift.product.media -%}
<div class="cart-gift">
{%- if gift_media.url -%}
<img src="{{ gift_media.url | image_url: width: 160 }}" alt="" loading="lazy">
{%- endif -%}
<div>
<p>{{ 'cart.gift_unlocked' | t }}</p>
<p>{{ gift.product.title }}{% if gift.variant %} · {{ gift.variant.title }}{% endif %}</p>
<p class="muted">{{ gift.discount_name }}</p>
</div>
{% form 'cart_add' %}
<input type="hidden" name="product_id" value="{{ gift.product_id }}">
{%- if gift.variant_id -%}<input type="hidden" name="variant_id" value="{{ gift.variant_id }}">{%- endif -%}
<input type="hidden" name="quantity" value="{{ gift.remaining_quantity }}">
<button type="submit">{{ 'cart.claim_gift' | t }}</button>
{% endform %}
</div>
{%- endif -%}
{%- endfor -%}
Mark gift lines#
A gift in the cart, claimed or added automatically, is a cart line with line.is_free_gift set to true. Label it as a free gift and leave out the quantity control, because the gift's quantity is set by the offer. Keep the remove control so the shopper can decline the gift.
{%- for line in cart.lines -%}
<div class="cart-line">
<p>{{ line.title }}</p>
{%- if line.is_free_gift -%}
<span class="badge">{{ 'cart.free_gift' | t }}</span>
{%- else -%}
{%- comment -%} your quantity stepper {%- endcomment -%}
{%- endif -%}
{%- comment -%} your remove button, for every line {%- endcomment -%}
</div>
{%- endfor -%}
Checklist#
The cart drawer and the cart page both show offers and gift lines.
Gift text (the heading, the add button and the badge) comes from your locale files, not from the template.
A gift whose
productis null is skipped.In the theme editor, the offer appears only when the cart really has one; do not show placeholder gifts.