I'd build a cart-validation Function that runs at checkout and blocks orders when subscription items are mixed with one-time purchases above the threshold. The merchant would tag subscription products in their metafields, and I'd pair that with a client-side banner so customers understand why checkout failed and how to fix it.
A subscription-focused DTC merchant needed to enforce a fulfillment constraint: preventing customers from mixing subscription and one-time purchase items in the same cart when the one-time subtotal exceeds $500. The merchant's logistics couldn't handle fulfilling mixed subscriptions and high-value one-time orders together.
Four pieces
Checkout
Cart Validation Function
Blocks checkout and shows a message if the cart contains subscription items mixed with one-time purchases totaling over $500.
query Input {
cart {
lines {
id
quantity
merchandise {
__typename
... on ProductVariant {
id
title
product {
id
title
handle
metafield(namespace: "custom", key: "is_subscription") {
value
}
}
}
}
cost {
totalAmount {
amount
}
}
attribute(key: "subscription_frequency") {
value
}
}
}
}
Metafield namespace is 'custom' and key is 'is_subscription'; set to true/false in product metafields. The attribute key 'subscription_frequency' is optional but helps identify subscription lines.
Products
Subscription Tagging Helper
A snippet that tags each product metafield to flag whether it's a subscription item so the Function knows which to watch.
{% # Show this in your checkout or cart if mixed subscription/one-time detected %}
{% assign has_sub = false %}
{% assign has_onetime = false %}
{% assign onetime_total = 0 %}
{% for item in cart.items %}
{% if item.properties.subscription_frequency %}
{% assign has_sub = true %}
{% else %}
{% assign has_onetime = true %}
{% assign onetime_total = onetime_total | plus: item.price | times: item.quantity %}
{% endif %}
{% endfor %}
{% if has_sub and has_onetime and onetime_total > 50000 %}
<div style="background: #fee2e2; border: 1px solid #fecaca; padding: 12px 16px; border-radius: 6px; margin: 16px 0;">
<p style="margin: 0; color: #991b1b; font-weight: 500; font-size: 14px;">
⚠️ We can't mix subscription and one-time purchases above $500 in one order. Please split into separate carts or remove items.
</p>
</div>
{% endif %}
This detects the condition client-side; the Function is server-side enforcement. Use together for best UX.
Admin
Metafield Bulk Updater
A small script to mark multiple products as subscriptions at once so you don't have to edit each one manually in the product editor.
Admin API script
Admin GraphQL explorergraphql
# Admin GraphQL
# Run this once per product or batch to set the subscription flag.
# Replace PRODUCT_ID with the actual product ID and set value to "true" or "false".
mutation SetSubscriptionFlag($metafields: [MetafieldsSetInput!]!) {
metafieldsSet(metafields: $metafields) {
metafields {
id
namespace
key
value
}
userErrors {
field
message
}
}
}
# Variables (paste in Variables panel):
# {
# "metafields": [
# {
# "ownerId": "gid://shopify/Product/PRODUCT_ID_1",
# "namespace": "custom",
# "key": "is_subscription",
# "type": "boolean",
# "value": "true"
# },
# {
# "ownerId": "gid://shopify/Product/PRODUCT_ID_2",
# "namespace": "custom",
# "key": "is_subscription",
# "type": "boolean",
# "value": "true"
# }
# ]
# }
Replace PRODUCT_ID_1, PRODUCT_ID_2, etc. with real product IDs. You can batch up to ~25 products per call to avoid rate limits.
Got a similar problem?
Sketch your build in 30 seconds — voice, type, or attach a screenshot.