Upgrade to subscription block
Overview
Loop's native Checkout Upgrade block lets shoppers convert a one-time purchase into a subscription directly inside Shopify Checkout, with a dropdown to pick the delivery frequency. This works out of the box for most merchants. Learn more
However, some brands want deeper UI control in checkout, for example:
- Layout: Showing each delivery frequency as an individual button / card / radio button instead of a dropdown to make the choices more discoverable.
- Badges: Adding badges like "Popular" or "Best value" on specific plans
- Business logic: Conditionally showing or ordering frequencies based on brand-specific logic
- Styling: Change styles of button, text and other components
Because Shopify's Checkout UI Extensibility restricts custom CSS on first-party app blocks, the right way to achieve this is for the brand's development team (or agency) to build a custom Shopify Checkout UI extension that uses Loop's selling plan data and applies the shopper's choice to the cart line. This guide walks you through how to do that.
How it works?
At a high level, your custom Checkout UI extension will:
- Fetch the eligible selling plans for each product in the cart from the Shopify Storefront API, including Loop's checkout availability metafields that drive selling plan visibility on Checkout blocks.
- Filter and sort the plans using those metafields — only show plans flagged as available on checkout, in the order Loop has configured.
- Render your custom UI (buttons, radios, cards) using Shopify's Checkout UI extension components.
- Apply the selected selling plan to the relevant cart line using applyCartLinesChange.
Loop continues to manage the entire subscription lifecycle downstream - contract creation, recurring orders, dunning, the customer portal, and so on. Your extension only owns the frequency selection UI at checkout.
Prerequisites
Before you start, make sure:
- Store is on Loop Pro plan to access Checkout upgrade feature.
- Store is on Shopify Plus plan (required for most Checkout UI Extensibility targets).
- Loop's native Checkout Upgrade block is disabled for the targets where your custom extension will render. Otherwise, both UIs will appear simultaneously.
- Selling plans are created and configured in the Loop admin - not directly in Shopify. Loop writes the checkout availability metafields your extension will read.
- Familiarity with Shopify Checkout UI Extensions. Learn more
Step 1: Fetch eligible selling plans
For each product in the cart, query the Shopify Storefront API to fetch its selling plans along with the Loop metafields that govern checkout behavior.
You can refer the following GraphQL query (specifically line 101-109)
query ProductDetails(
$id: ID!
$metafieldPrepaidKey: String!
$metafieldCheckoutUpgradeKey: String!
$metafieldDefaultSellingPlanKey: String!
$metafieldSellingPlanGroupPositionKey: String!
) {
product(id: $id) {
sellingPlanGroups(first: 10) {
edges {
node {
sellingPlans(first: 10) {
edges {
node {
id
name
deliveryPolicy {
... on SellingPlanRecurringDeliveryPolicy {
__typename
interval
intervalCount
}
}
isDefaultSellingPlan: metafield(key: $metafieldDefaultSellingPlanKey) {
value
}
sellingPlanGroupPosition: metafield(key: $metafieldSellingPlanGroupPositionKey) {
value
}
priceAdjustments {
adjustmentValue {
... on SellingPlanFixedAmountPriceAdjustment {
__typename
adjustmentAmount {
amount
currencyCode
}
}
... on SellingPlanFixedPriceAdjustment {
__typename
price {
amount
currencyCode
}
}
... on SellingPlanPercentagePriceAdjustment {
__typename
adjustmentPercentage
}
}
}
}
}
}
}
}
}
variants(first: 50) {
edges {
node {
id
price {
amount
currencyCode
}
sellingPlanAllocations(first: 50) {
edges {
node {
sellingPlan {
id
name
deliveryPolicy {
... on SellingPlanRecurringDeliveryPolicy {
__typename
interval
intervalCount
}
}
priceAdjustments {
adjustmentValue {
... on SellingPlanFixedAmountPriceAdjustment {
__typename
adjustmentAmount {
amount
currencyCode
}
}
... on SellingPlanFixedPriceAdjustment {
__typename
price {
amount
currencyCode
}
}
... on SellingPlanPercentagePriceAdjustment {
__typename
adjustmentPercentage
}
}
}
isSpgAvailableOnCheckout: metafield(key: $metafieldCheckoutUpgradeKey) {
value
}
isDefaultSellingPlan: metafield(key: $metafieldDefaultSellingPlanKey) {
value
}
sellingPlanGroupPosition: metafield(key: $metafieldSellingPlanGroupPositionKey) {
value
}
}
}
}
}
}
}
}
}
}Metafields keys for GraphQL query
Loop stores its checkout configuration on selling-plan metafields. Pass the following keys as query variables to get relevant data:
| Variable | What it controls |
|---|---|
metafieldCheckoutUpgradeKey | Whether the selling plan should be shown on checkout (isSpgAvailableOnCheckout) |
metafieldDefaultSellingPlanKey | Whether this plan is the default / popular option |
metafieldSellingPlanGroupPositionKey | The display order of each plan |
Note: These keys are consistent across all Loop merchants but must be passed accurately for the query to return values.
Step 2: Filter, sort, and identify the default plan
Once you have the response, on the variant's sellingPlanAllocations:
- Filter plans where
isSpgAvailableOnCheckout.value === "true". This will give you a list of all the selling plans marked as available on Checkout. Manage availability of selling plans by visiting Loop admin > Acquire > Selling plans > Specific selling plan > Availability > Channels. - Sort the filtered plans by the numeric value of
sellingPlanGroupPosition. This is the order the merchant has configured in Loop. - Identify the default plan by finding the plan where
isDefaultSellingPlan.value === "true". Use this to pre-select that frequency on render and maybe display a "Recommended" badge.
The final output is a clean, ordered list of selling plans ready to render.
Step 3: Render your custom UI
Use Shopify's Checkout UI Extension components to render each selling plan as a button, radio, or card. The choice of components is yours - Loop has no requirement here, as long as the shopper can pick exactly one frequency per cart line (or choose one-time purchase).
Refer to the Shopify Checkout UI Extensions component for the available components and styling primitives.
Step 4: Apply the selected selling plan to the cart line
When the shopper picks a purchase option, specific frequency or one-time, apply it to the relevant cart line using the applyCartLinesChange hook from @shopify/ui-extensions-react/checkout.
You can refer below snippet
await applyCartLinesChange({
type: "updateCartLine",
id: line.id,
sellingPlanId: plan.id,
attributes: [
...line.attributes,
],
});where:
line.idis the ID of the cart line being upgraded to a subscriptionplan.idis the selling plan ID of the frequency the shopper selected (from Step 2)attributespreserves any existing line attributes — important if Loop or the merchant has already attached metadata to the line
For a one-time purchase selection, omit sellingPlanId (or pass null) to remove any previously applied selling plan from the line.
Once applyCartLinesChange resolves successfully, Shopify will recalculate the cart with the subscription pricing, and the order once placed will flow into Loop as a subscription contract automatically. No additional API call to Loop is required.
Best practices
- Disable Loop's native Checkout Upgrade block on the same target before deploying your extension. Running both will render a duplicate UI to the shopper.
- Handle the "no eligible plans" case gracefully - if a product has no selling plans flagged as available on checkout, make sure not to render an empty card.
- Cap the number of buttons rendered. If a product has more than 4-5 frequencies, consider falling back to a dropdown or a "Show more" pattern to avoid an unusable layout.
- Test with multiple cart-line scenarios, specifically mixed carts (subscription + one-time), products with different selling plan groups, and prepaid plans.
- Re-run the query when the cart changes. A shopper adding a new product to the cart should trigger a fresh fetch for that product.
- Respect Loop's selling plan IDs as the source of truth. Do not create or modify selling plans directly in Shopify - always manage them from the Loop admin so the checkout availability and other metafields stay in sync.
Need help?
If you run into issues - incorrect metafield values, missing selling plans on the response, or unexpected behavior after applyCartLinesChange - reach out to your Loop CSM or write to [email protected] with:
- The store's
myshopify.comURL - The product ID and variant ID being tested
- The full request and response from the GraphQL query
- A screenshot of your extension and the Loop admin selling plan configuration