/cart/add
{% form 'cart_add' %}
quantity defaults to 1; variant_id is required for products with variants.
Form fields
-
product_idRequired Product to add.
-
variant_idOptional Required when the product has variants.
-
quantityOptional Defaults to 1.
-
properties[<namespace>.<key>]…Optional Declared buyer-writable cart-line properties.
-
_formatOptional Set to json when submitting with fetch; omit for the normal browser redirect.
-
client_mutation_idOptional Unique retry key, up to 128 characters. Reusing it for the same action prevents a successful mutation from being applied twice for 24 hours.
Validation
Positive product/variant ids; variant must belong to the product; quantity, inventory, purchase state, B2B availability, and declared buyer-writable properties are validated server-side.
Limits
Quantity defaults to 1. The final line quantity cannot exceed product.quantity_max (or the selected variant's quantity_max), which resolves stock, backorder capacity, and the merchant's variant → product → store order cap. At most 20 properties per line.
Response
{
success,
cart,
user_errors
}
cart is the current enriched cart and is unchanged when refused.
/cart/clear
{% form 'cart_clear' %}
Empties the cart.
Form fields
-
_formatOptional Set to json when submitting with fetch; omit for the normal browser redirect.
-
client_mutation_idOptional Unique retry key, up to 128 characters. Reusing it for the same action prevents a successful mutation from being applied twice for 24 hours.
Validation
Requires a valid visitor/customer cart owner and is unavailable on the B2B runtime.
Limits
No action-specific numeric limit.
Response
{
success,
cart,
user_errors
}
/cart/notify-me
{% form 'cart_notify_me' %}
Back-in-stock subscription; guests supply contact details, customers default to their account email.
Form fields
-
product_idRequired Out-of-stock product to watch.
-
variant_idOptional Specific variant to watch.
-
contact_typeOptional email or sms; defaults to email.
-
emailOptional Required for guests using email contact.
-
phoneOptional Required when contact_type is sms.
Validation
Positive product/variant ids; restock alerts and guest requests must be enabled; contact_type is email or sms. Logged-in email requests use the account email. SMS requires an active messaging provider and stores a valid E.164 number.
Limits
Email is limited to 256 characters. Requests expire after the merchant-configured 1–365 day window. Repeated requests are not deduplicated.
Response
{
success,
response,
user_errors
}
response is the localized saved message.
/cart/remove
{% form 'cart_remove' %}
Removes the product (or specific variant) line.
Form fields
-
product_idRequired Cart line's product.
-
variant_idOptional Cart line's variant, when the product has variants.
-
line_idOptional Required to target a line carrying properties.
-
_formatOptional Set to json when submitting with fetch; omit for the normal browser redirect.
-
client_mutation_idOptional Unique retry key, up to 128 characters. Reusing it for the same action prevents a successful mutation from being applied twice for 24 hours.
Validation
Positive product/variant ids; line_id targets one exact line. Bare product/variant targeting only matches a property-less line.
Limits
No action-specific numeric limit.
Response
{
success,
cart,
user_errors
}
/cart/update
{% form 'cart_update' %}
Sets the line to quantity; 0 removes it.
Form fields
-
product_idRequired Cart line's product.
-
variant_idOptional Cart line's variant, when the product has variants.
-
quantityRequired New quantity; 0 removes the line.
-
line_idOptional Required to target a line carrying properties.
-
_formatOptional Set to json when submitting with fetch; omit for the normal browser redirect.
-
client_mutation_idOptional Unique retry key, up to 128 characters. Reusing it for the same action prevents a successful mutation from being applied twice for 24 hours.
Validation
Positive product/variant ids and integer quantity; quantity 0 removes. line_id is required for a line with properties and cannot select another visitor's line.
Limits
The final line quantity cannot exceed the current cart line's quantity plus quantity_can_add. Inventory, backorder, preorder, and the resolved variant → product → store order cap are enforced.
Response
{
success,
cart,
user_errors
}
cart is the current enriched cart and is unchanged when refused.
/cart/discount/apply
{% form 'discount_code_apply' %}
Validates the code against the current cart, customer, market, and stacking rules, then stores it only when it can apply. Unknown or ineligible codes leave the cart unchanged.
Form fields
-
codeRequired The discount code to apply.
-
_formatOptional Set to json when submitting with fetch; omit for the normal browser redirect.
-
client_mutation_idOptional Unique retry key, up to 128 characters. Reusing it for the same action prevents a successful mutation from being applied twice for 24 hours.
Validation
Non-empty code; the same store-scoped pricing evaluator used to advertise available discounts validates code existence, activity, eligibility, stacking, market, customer, and current cart conditions before the cart changes.
Limits
Applied-code count and discount limits come from order-pricing configuration.
Response
{
success,
cart,
user_errors
}
ineligible codes return the unchanged current cart.
/cart/discount/remove
{% form 'discount_code_remove' %}
Removes the applied code (no-op when absent).
Form fields
-
codeRequired The applied code to remove.
-
_formatOptional Set to json when submitting with fetch; omit for the normal browser redirect.
-
client_mutation_idOptional Unique retry key, up to 128 characters. Reusing it for the same action prevents a successful mutation from being applied twice for 24 hours.
Validation
Non-empty code; removing a code that is not applied is an idempotent no-op.
Limits
No action-specific numeric limit.
Response
{
success,
cart,
user_errors
}
User error codes#
Code |
When |
|---|---|
STOREFRONT_CART_PRODUCT_INVALID |
product_id missing or not purchasable. |
STOREFRONT_CART_VARIANT_INVALID |
variant_id missing for a variable product, or wrong. |
STOREFRONT_CART_OWNER_REQUIRED |
No usable visitor session — reload the page. |
Quantity, stock, and purchase-state validation failures come back the same way — as user_errors with the CURRENT unchanged cart, never as a broken state. After a successful action, ctx.cart reflects the change on the next render.
Line properties#
A cart line can carry declared properties — the personalization channel (engraving text, a gift note). Every property key must match one of the store's cart line definitions (Settings → Metafield definitions → Cart lines, or an app's declaration), written as "
{% form 'cart_add' %}
<input type="hidden" name="product_id" value="{{ product.id }}">
<input name="properties[custom.engraving]" maxlength="30" placeholder="{{ 'products.engraving' | t }}">
<button type="submit">{{ 'products.add_to_cart' | t }}</button>
{% endform %}
Properties are part of LINE IDENTITY: the same product with different property values is a SEPARATE cart line (two mugs, two engravings), each with its own line_id. Cart line forms (quantity steppers, remove buttons) must post the line's line_id — bare product/variant targeting matches only the property-less line, never silently one of several personalized lines.
{% for line in cart.items %}
{% for key, value in line.properties %}<p>{{ value }}</p>{% endfor %}
{% form 'cart_remove' %}
<input type="hidden" name="product_id" value="{{ line.product_id }}">
<input type="hidden" name="line_id" value="{{ line.line_id }}">
<button type="submit">{{ 'cart.remove' | t }}</button>
{% endform %}
{% endfor %}
Code |
When |
|---|---|
CART_PROPERTY_UNKNOWN |
The key matches no cart line definition on this store. |
CART_PROPERTY_INVALID |
The value fails the definition's type or rules. |
CART_PROPERTY_NOT_WRITABLE |
The definition is not buyer-writable (hidden), or an app wrote outside its namespace. |
CART_PROPERTY_TOO_MANY |
More properties than one line allows. |