Introduction
If you run a Shopify store, you already know that getting a customer to the checkout page is half the battle. The other half? Making sure you maximize the value of every single order. That is exactly where Shopify checkout UI extensions come in.
Checkout upsells are one of the most effective ways to increase average order value (AOV) without spending an extra dollar on ads. Instead of letting customers complete their purchase with only what they originally added to the cart, you show them a relevant, well-timed offer right at the checkout and they can accept it in one click.
In the past, customizing the Shopify checkout required editing checkout.liquid, which was exclusively available to Shopify Plus merchants. Today, Shopify has opened up checkout customization for all merchants through Shopify checkout UI extensions, a developer-friendly framework that lets you inject custom blocks, banners, upsell widgets, and more directly into the checkout flow without touching a single line of theme code.
This guide walks you through everything you need to know: what checkout UI extensions are, why upsells work, how the extension architecture is structured, and a full step-by-step process to build and deploy your first checkout upsell using Shopify's extension framework.
What Are Shopify Checkout UI Extensions?
Shopify checkout UI extensions are sandboxed UI components that developers can build and deploy to extend the checkout experience on any Shopify store. They are part of Shopify's broader "extensibility" model introduced with Shopify Functions and the new checkout architecture.
Unlike theme customizations, checkout UI extensions:
- Run in a secure, sandboxed environment inside Shopify's checkout
- Do not require access to checkout.liquid
- Are available to all Shopify plans (not just Plus)
- Are built using React and Shopify's @shopify/ui-extensions-react package
- Support a range of extension targets - specific positions in the checkout where your UI can appear
Shopify defines these extension targets as fixed slots in the checkout layout. For upsells, the most commonly used targets are:
- purchase.checkout.block.render - a flexible block that can be placed almost anywhere in checkout
- purchase.checkout.cart-line-item.render-after - renders after each line item
- purchase.checkout.actions.render-before - renders just before the payment/place order button
These targets ensure that your upsell widget appears in a contextually relevant position without disrupting the native checkout flow.
Why Checkout Upsells Work (and Why You Should Implement Them Now)
Before diving into the technical implementation, it is worth understanding the business case.
- Customers Are Already in a Buying Mindset: At the checkout stage, your customer has already made the psychological decision to spend money. Adding a relevant product offer at this moment when purchase intent is at its peak sees significantly higher conversion rates than pop-ups or product page suggestions.
- One-Click Accept Reduces Friction: Shopify checkout UI extensions allow you to build upsells that add a product to the order with a single button click. The customer does not leave the checkout page, does not re-enter payment details, and does not start the checkout process over. The reduced friction directly translates to higher acceptance rates.
- Higher Average Order Value Without More Ad Spend: Acquiring a new customer is expensive. Selling more to an existing customer mid-transaction costs virtually nothing by comparison. Even a 10–15% upsell acceptance rate on a $15 product offer across thousands of orders adds up to significant revenue.
- Personalization at Scale: Using Shopify's @shopify/ui-extensions APIs, you can access cart data, customer information, and product metadata to show upsells that are genuinely relevant to what the customer is already buying.
Prerequisites
Before you build a checkout upsell using Shopify checkout UI extensions, make sure you have the following in place:
- Node.js v18 or later installed
- Shopify CLI v3.x installed globally (npm install -g @shopify/cli)
- A Shopify Partner account and a connected development store
- Basic familiarity with React and JavaScript/TypeScript
- A Shopify app already created (or a new one you will scaffold with the CLI)
Step 1: Create a New Shopify App (or Use an Existing One)
If you are starting fresh, scaffold a new Shopify app using the CLI:
shopify app create node
Follow the prompts to name your app and connect it to your Partner account. Once the app is created, navigate into the project directory:
cd your-app-name
Step 2: Add a Checkout UI Extension to Your App
Inside your app directory, run the following command to generate a new checkout UI extension:
shopify app generate extension
When prompted:
- Select Checkout UI Extension from the list of extension types
- Give your extension a name, for example: checkout-upsell
- Choose React as your language preference
This scaffolds a new folder inside your app at extensions/checkout-upsell/ with the following structure:
extensions/
checkout-upsell/
src/
Checkout.jsx ← Your main extension component
shopify.extension.toml ← Extension configuration file
package.json
Step 3: Configure the Extension Target in shopify.extension.toml
Open shopify.extension.toml. This file defines where your extension appears in the checkout and what capabilities it has access to.
name = "Checkout Upsell" type = "ui_extension" [[extensions]] name = "Checkout Upsell" handle = "checkout-upsell" type = "ui_extension" [[extensions.targeting]] module = "./src/Checkout.jsx" target = "purchase.checkout.block.render" [extensions.capabilities] api_access = true network_access = false
Step 4: Build the Upsell Component
Now open src/Checkout.jsx. This is where you write your upsell UI using Shopify's React-based UI primitives.
Here is a complete working example of a checkout upsell that recommends a related product and allows the customer to add it to their order with one click:
import {
reactExtension,
useCartLines,
useApplyCartLinesChange,
useSettings,
BlockStack,
Button,
Heading,
Image,
InlineStack,
Text,
Divider,
useApi,
} from "@shopify/ui-extensions-react/checkout";
import { useState, useEffect } from "react";
export default reactExtension("purchase.checkout.block.render", () => (
));
function CheckoutUpsell() {
const { query } = useApi();
const applyCartLinesChange = useApplyCartLinesChange();
const cartLines = useCartLines();
const [upsellProduct, setUpsellProduct] = useState(null);
const [added, setAdded] = useState(false);
const [loading, setLoading] = useState(false);
// Hardcoded upsell product handle — in production, make this dynamic
const UPSELL_PRODUCT_HANDLE = "premium-protection-plan";
useEffect(() => {
// Check if upsell product is already in cart
const alreadyInCart = cartLines.some(
(line) => line.merchandise.product.handle === UPSELL_PRODUCT_HANDLE
);
if (alreadyInCart) {
setAdded(true);
return;
}
// Fetch product data via Storefront API
query(
`query getProduct($handle: String!) {
productByHandle(handle: $handle) {
id
title
featuredImage {
url
altText
}
variants(first: 1) {
edges {
node {
id
price {
amount
currencyCode
}
}
}
}
}
}`,
{ variables: { handle: UPSELL_PRODUCT_HANDLE } }
).then(({ data }) => {
if (data?.productByHandle) {
setUpsellProduct(data.productByHandle);
}
});
}, []);
if (!upsellProduct || added) return null;
const variant = upsellProduct.variants.edges[0]?.node;
const price = variant?.price;
const handleAddToCart = async () => {
setLoading(true);
const result = await applyCartLinesChange({
type: "addCartLine",
merchandiseId: variant.id,
quantity: 1,
});
if (result.type === "success") {
setAdded(true);
}
setLoading(false);
};
return (
Frequently Bought Together
{upsellProduct.featuredImage && (
)}
{upsellProduct.title}
{price && (
{price.currencyCode} {parseFloat(price.amount).toFixed(2)}
)}
);
}
What This Code Does
- useCartLines - reads the current items in the cart so you can avoid showing an upsell for something the customer already has
- useApi().query - makes a Storefront API call to fetch product details by handle
- useApplyCartLinesChange - applies a cart mutation to add the upsell product directly to the order, right from the checkout page
- Shopify UI primitives (BlockStack, InlineStack, Text, Button, Image) - these are the only components you can use inside a checkout extension; custom HTML and CSS are not permitted in the sandbox
Step 5: Run the Extension Locally for Development
Start your development server:
shopify app dev
This command authenticates your CLI, spins up a local tunnel, and pushes your extension to your development store in preview mode.
Shopify CLI will output a URL. Open your Shopify admin, navigate to Sales Channels → Online Store → Customize, then switch to the Checkout editor. You should see your extension available as a block that you can drag and position on the checkout page.
Step 6: Use the Checkout Editor to Position Your Upsell
Once deployed to a store, merchants can manage the position of your extension using Shopify's no-code Checkout Editor:
- Go to Shopify Admin → Settings → Checkout
- Click Customize
- In the left panel, find your extension under Apps
- Drag it to the desired position in the checkout layout typically just above the payment section or below the order summary
- Click Save
This gives non-technical merchants full control over placement without requiring any code changes.
Step 7: Make the Upsell Product Configurable via Extension Settings
Hard-coding a product handle is fine for testing but impractical for production. Shopify checkout UI extensions support merchant-configurable settings defined in shopify.extension.toml.
Add this block to your TOML file:
[[extensions.settings.fields]]
key = "upsell_product_handle"
type = "single_line_text_field"
name = "Upsell Product Handle"
description = "Enter the product handle of the item you want to upsell at checkout"
Then update your component to read this setting:
const { upsell_product_handle } = useSettings();
const UPSELL_PRODUCT_HANDLE = upsell_product_handle || "default-product-handle";
Now, from the Checkout Editor, merchants can type in any product handle and change the upsell offer without touching a single line of code.
Step 8: Deploy the Extension to Production
When you are ready to go live:
shopify app deploy
This builds and pushes the extension to Shopify's CDN. Once deployed, any store that installs your app will have access to the extension. If it is a private app for a single store, it immediately becomes available in that store's Checkout Editor.
Best Practices for Checkout Upsells with Shopify Checkout UI Extensions
Keep It Contextually Relevant
The most effective upsells are directly related to what the customer is buying. A customer buying a camera is far more likely to add a memory card than a random best-seller. Use cart data (useCartLines) to tailor your offer dynamically.
Keep the Price Low and the Value Clear
High-ticket upsells at checkout rarely convert. Aim for products priced at 10–30% of the cart total, and make the value proposition instantly obvious: warranty plans, accessories, bundles, and consumables all work well.
Avoid Adding Friction
Your upsell block should not be the first thing a customer sees. Position it below the order summary or just above the payment section so it feels supplementary, not obstructive.
Test Multiple Offers
Use Shopify's A/B testing capabilities or an analytics layer to measure which upsell products perform best. Acceptance rate, revenue impact, and order abandonment should all be tracked.
Respect the Sandbox Limitations
Shopify checkout UI extensions run in a secure sandbox. You cannot use arbitrary JavaScript libraries, make unrestricted network requests, or access browser APIs like localStorage or documents. Work within Shopify's provided APIs and UI components to ensure your extension is approved and performs reliably.
Common Mistakes to Avoid
| Mistake | Why It Hurts | Fix |
|---|---|---|
| Showing the upsell if the product is already in the cart | Creates a confusing duplicate add | Check cart lines on load and conditionally render |
| Using an invalid product handle | Extension silently fails, no upsell shown | Always validate product data before rendering |
| Overloading the checkout with multiple upsell blocks | Increases cart abandonment | Limit to one focused, high-quality upsell |
| Not testing on mobile | Checkout is used heavily on mobile | Preview in Shopify's mobile checkout view |
| Ignoring extension settings | Forces code changes for every product swap | Use useSettings() for merchant-controlled config |
Advanced Concepts to Explore Next
Once you have a basic checkout upsell working, here are advanced patterns you can layer in:
- Dynamic upsell logic using Shopify Functions - use Shopify Functions to apply discounts or bundle pricing when the upsell item is added
- Product recommendation APIs - integrate with Shopify's product recommendation engine to serve AI-powered, personalized upsells
- Tiered upsells - show a different offer based on cart total (e.g., "Add $10 more for free shipping" vs. "Complete your kit")
- Metafield-driven offers - tag upsell relationships directly on products using metafields, then read those from the extension to create a fully automated recommendation engine
Conclusion
Shopify checkout UI extensions represent a fundamental shift in how developers and merchants can customize the checkout experience. Gone are the days of waiting for a Shopify Plus plan or hacking together workarounds in checkout.liquid. Today, any Shopify merchant can deploy a polished, performant, and secure checkout upsell using the extension framework covered in this guide.
The implementation steps are straightforward: scaffold your extension with Shopify CLI, write your upsell component using Shopify's React-based UI primitives, configure extension settings for merchant flexibility, and deploy using shopify app deploy. From there, the Checkout Editor handles positioning and the Storefront API handles product data leaving you to focus on what matters most: crafting an offer compelling enough that customers actually click "Add to Order."
Start with one upsell, measure its impact on your AOV, and iterate. The checkout is one of the highest-leverage surfaces in your entire store and with Shopify checkout UI extensions, it is finally yours to optimize.
Frequently Asked Questions
Do I need Shopify Plus to use checkout UI extensions?
No. Checkout UI extensions are available on all Shopify plans. Shopify Plus is required only if you want to edit the checkout using checkout.liquid (the legacy approach).
Can I show more than one upsell in the checkout?
Technically yes, but it is not recommended. Multiple offers compete with each other and can increase cart abandonment. One focused, high-quality upsell outperforms multiple mediocre ones.
Will checkout UI extensions slow down my checkout page?
No. Shopify loads extensions asynchronously and is sandboxed from the main checkout thread. The native checkout performance is not impacted.
Can I use third-party libraries inside a checkout UI extension?
Only a limited set of approved libraries is available in the extension sandbox. You must use Shopify's provided UI components and APIs. Arbitrary npm packages that interact with the DOM cannot be used.
How do I track whether a customer accepted my upsell offer?
Use Shopify's Order webhook (orders/create) and check if the upsell product variant ID is present in the line items. You can also use Shopify Analytics or a third-party analytics tool connected via the Pixel extension.

