Tom Sailors
· Refreshed
Brief · Anonymized case study

Subscription and One-Time Mixed-Cart Blocker

Start by marking subscription products in the admin with a custom metafield flag. Build a Shopify Function that reads this flag and rejects any cart containing both subscription and one-time items where the one-time total crosses $500. Layer a client-side banner in the cart to surface the rule before checkout. Use the Admin API to batch-set the subscription flag across the catalog.

A DTC merchant with a subscription-first product line cannot reliably fulfill orders that mix subscription and one-time purchases—especially when the one-time portion exceeds $500. The business needs a way to prevent these invalid mixed carts from reaching checkout so that customers understand the constraint upfront and orders ship on the intended cadence.
Four pieces
Checkout

Cart Validation Function

Blocks checkout and shows an error 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 displays whether a product is flagged as a subscription item via its custom metafield, so the cart validation Function knows which products 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

Renders a red warning banner at checkout explaining the mixed-cart constraint if the Function detects a violation, so customers know exactly why their order was blocked.

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 GraphQL mutation to batch-flag multiple products as subscription items at once, avoiding manual per-product edits in the admin.

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. 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 →