Brief · Anonymized case study
Blocking Mixed Subscription and One-Time Checkout
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.
Shopify Function
extensions/checkout-validation/src/run.graphql
function
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.
Theme extension
theme/snippets/subscription-flag.liquid
liquid
{% # Add this to a product page section or app block to set/review subscription flags %}
{% if product.metafields.custom.is_subscription %}
{% assign is_sub = product.metafields.custom.is_subscription.value %}
{% else %}
{% assign is_sub = false %}
{% endif %}
<div class="subscription-flag" style="padding: 12px; background: #f5f5f5; border-radius: 4px; margin: 16px 0;">
<p style="margin: 0; font-size: 14px; font-weight: 500;">
Subscription Item:
{% if is_sub == 'true' or is_sub == true %}
<span style="color: #0a7d3d;">✓ Yes</span>
{% else %}
<span style="color: #6b7280;">No</span>
{% endif %}
</p>
</div>
This is read-only display; to actually set the metafield, use the Admin API mutation in piece four or edit directly in admin product editor.
Storefront
Checkout Error Banner
Shows a red banner at checkout explaining the rule if the cart validation Function detects a mixed-subscription violation.
Checkout UI extension
theme/snippets/cart-validation-banner.liquid
liquid
{% # 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 explorer
graphql
# 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.
Sketch the build →