How to Build a Shopify Checkout UI Extension in 20 Minutes (Step-by-Step)
What you will achieve in this guide
Follow this step-by-step documentation to configure, test, and deploy this feature to your live Shopify store.
Developer guide: This article covers a custom implementation rather than configuring Rivio. Checkout UI extensions on information, shipping, and payment require Shopify Plus. Review the current Shopify API documentation before using older code samples.
With Shopify officially deprecating checkout.liquid, Shopify checkout customization has entered a new era. Today, the standard for tailoring the purchase funnel is Shopify Checkout Extensibility—powered by app-based checkout components called Shopify Checkout UI Extensions.
Whether you want to offer 1-click upsells, collect custom order notes, display trust badges, or configure a delivery date picker, mastering checkout extensions is now essential.
In this quick guide, you will learn how to build and deploy a functional pre-purchase product offer checkout extension in 20 minutes using the Shopify CLI and React.
Short on time or prefer a no-code workflow?
If you don’t want to write and maintain custom React apps, configure GraphQL endpoints, and manage hosting infrastructure, you can design high-converting checkouts in minutes using the Rivio Checkout Customizer on the Shopify App Store.
Why Checkout UI Extensions Matter for Shopify Checkout Customization
Historically, merchants customized their checkout by injecting custom scripts and HTML directly into checkout.liquid. While flexible, this approach caused security vulnerabilities, slow page loads, and broke during major checkout updates.
Shopify Checkout UI Extensions solve these problems by providing:
- Sandboxed Performance: Custom blocks run in isolated environments without slowing down Shopify’s checkout engine.
- Native Theme Styling: UI components automatically adapt to your merchant brand colors, typography, and corner radius.
- Frictionless Buyer Experience: Seamless integration with Shopify Pay, Shop Pay, and accelerated checkouts.
- Future-Proof Upgrades: Safe from theme updates and Shopify core releases.
Let’s walk through building your first custom checkout UI extension from scratch.
Prerequisites
Before starting, ensure you have the following installed:
- Node.js (v18.20.0 or higher / LTS recommended)
- Shopify Partner Account with a Development Store (with Checkout Extensibility enabled)
-
Shopify CLI installed globally or run via
npm/npx
Step 1: Initialize Your Shopify App Project
Shopify UI extensions must live inside a Shopify App container. Run this command in your terminal:
npm init @shopify/app@latest
Follow the interactive prompts:
-
App Name:
my-checkout-extension-app -
Template: Select
Start with an empty apporRemix / Node
Once the project is created, navigate into your project folder:
cd my-checkout-extension-app
Step 2: Generate the Checkout UI Extension
Inside your app directory, tell the Shopify CLI to create a checkout UI extension:
npm run shopify app generate extension
When prompted:
- Choose Checkout UI Extension as the extension type.
- Enter a name for the extension (e.g.,
checkout-pre-purchase-offer). - Select TypeScript React or JavaScript React (we will use React for this guide).
This creates an extensions/checkout-pre-purchase-offer directory containing your configuration file (shopify.extension.toml) and source code (src/Checkout.jsx or src/index.jsx).
Step 3: Configure Permissions & Capabilities
To pull product offers into the checkout, your extension needs permission to query Shopify's Storefront API.
Open extensions/checkout-pre-purchase-offer/shopify.extension.toml and ensure your [capabilities] block allows API access:
name = "checkout-pre-purchase-offer"
type = "ui_extension"
[[targeting]]
module = "./src/Checkout.jsx"
target = "purchase.checkout.block.render"
[capabilities]
api_access = true
network_access = true
Step 4: Write the Extension Logic (Pre-Purchase Upsell)
Now, replace the contents of your entry file (e.g., src/Checkout.jsx) with the complete pre-purchase offer component below.
This code does three things:
- Queries the Storefront API for candidate upsell products.
- Checks cart contents to avoid recommending products the buyer already has in their cart.
- Renders a native UI card with an "Add" button that modifies cart lines directly via the
useApplyCartLinesChangehook.
import React, { useEffect, useState } from "react";
import {
reactExtension,
Divider,
Image,
Banner,
Heading,
Button,
InlineLayout,
BlockStack,
Text,
SkeletonText,
SkeletonImage,
useCartLines,
useApplyCartLinesChange,
useApi,
} from "@shopify/ui-extensions-react/checkout";
// Set entry point for checkout block
export default reactExtension("purchase.checkout.block.render", () => <App />);
function App() {
const { query, i18n } = useApi();
const applyCartLinesChange = useApplyCartLinesChange();
const lines = useCartLines();
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [adding, setAdding] = useState(false);
const [showError, setShowError] = useState(false);
// Fetch recommended products on component mount
useEffect(() => {
query(
`query ($first: Int!) {
products(first: $first) {
nodes {
id
title
images(first: 1) {
nodes {
url
}
}
variants(first: 1) {
nodes {
id
price {
amount
currencyCode
}
}
}
}
}
}`,
{ variables: { first: 5 } }
)
.then(({ data }) => {
if (data?.products?.nodes) {
setProducts(data.products.nodes);
}
})
.catch((err) => console.error("Error loading products:", err))
.finally(() => setLoading(false));
}, [query]);
// Loading skeleton state
if (loading) {
return (
<BlockStack spacing="loose">
<Divider />
<Heading level={2}>You might also like</Heading>
<InlineLayout spacing="base" columns={[64, "fill", "auto"]} blockAlignment="center">
<SkeletonImage aspectRatio={1} />
<BlockStack spacing="none">
<SkeletonText inlineSize="large" />
<SkeletonText inlineSize="small" />
</BlockStack>
<Button kind="secondary" disabled={true}>
Add
</Button>
</InlineLayout>
</BlockStack>
);
}
// Filter out products already present in the customer's cart
const cartVariantIds = lines.map((item) => item.merchandise.id);
const availableOffers = products.filter((product) => {
const variantId = product.variants.nodes[0]?.id;
return variantId && !cartVariantIds.includes(variantId);
});
if (availableOffers.length === 0) {
return null;
}
const offer = availableOffers[0];
const variant = offer.variants.nodes[0];
const imageUrl = offer.images.nodes[0]?.url || "";
const formattedPrice = i18n.formatCurrency(variant.price.amount, {
currency: variant.price.currencyCode,
});
const handleAddToCart = async () => {
setAdding(true);
const result = await applyCartLinesChange({
type: "addCartLine",
merchandiseId: variant.id,
quantity: 1,
});
setAdding(false);
if (result.type === "error") {
setShowError(true);
console.error(result.message);
}
};
return (
<BlockStack spacing="loose">
<Divider />
<Heading level={2}>Exclusive Checkout Deal</Heading>
<InlineLayout spacing="base" columns={[64, "fill", "auto"]} blockAlignment="center">
{imageUrl ? (
<Image
border="base"
borderWidth="base"
borderRadius="loose"
source={imageUrl}
description={offer.title}
aspectRatio={1}
/>
) : null}
<BlockStack spacing="none">
<Text size="medium" emphasis="strong">
{offer.title}
</Text>
<Text appearance="subdued">{formattedPrice}</Text>
</BlockStack>
<Button
kind="secondary"
loading={adding}
accessibilityLabel={`Add ${offer.title} to order`}
onPress={handleAddToCart}
>
Add
</Button>
</InlineLayout>
{showError && (
<Banner status="critical">
Could not add item to cart. Please try again.
</Banner>
)}
</BlockStack>
);
}
Step 5: Test and Preview in Checkout Editor
Run your development server from your terminal:
npm run dev
- Press
pto open the Shopify developer preview link. - In the console, open the Checkout Editor URL provided by the CLI.
- In the Checkout Editor, click Add App Block in your checkout sections (such as below the Order Summary or above the Payment step).
- Drag and reposition your new pre-purchase block into place and hit Save.
You now have a fully functional Shopify checkout UI extension running live in your preview!
The Bottlenecks of Custom Code vs. No-Code Customization
While building custom extensions using code gives developers full control, store owners and scaling brands frequently encounter challenges:
| Challenge | Building from Scratch (Code) | Using Rivio Checkout Customizer (No-Code) |
|---|---|---|
| Setup Time | Days to weeks (CLI, hosting, app infrastructure) | Under 5 minutes (install & drag-and-drop) |
| Maintenance | Manual updates, API deprecation fixes, server costs | Zero maintenance; fully managed cloud updates |
| Merchant Control | Requires developer tickets for text, image, or offer edits | Store staff can customize colors, banners, and rules directly |
| Flexibility | Rigidly coded for specific single tasks | Pre-built library of banners, upsells, trust badges, and fields |
The Faster Way: Customize Your Checkout with Rivio
If your goal is to boost conversion rates and increase Average Order Value (AOV) without maintaining a dedicated codebase, check out Rivio Checkout Customizer.
Built directly on Shopify’s Checkout Extensibility framework, Rivio provides an intuitive visual builder that lets you deploy high-converting checkout widgets instantly:
- 1-Click Pre-Purchase Upsells & Cross-Sells: Suggest high-margin add-ons right next to the order summary.
- Trust Badges & Social Proof: Add guarantee seals, SSL icons, and review snippets at key decision moments.
- Custom Checkout Fields: Capture gift messages, delivery instructions, VAT numbers, or order notes seamlessly.
- Dynamic Banners & Alerts: Highlight free-shipping thresholds, shipping delays, or limited-time promotional announcements.
Ready to upgrade your store's checkout?
Explore RivioApps.com or get started directly by installing the Rivio Checkout Customizer App on Shopify.
Frequently Asked Questions (FAQs)
1. What is a Shopify Checkout UI Extension?
A Shopify Checkout UI Extension is a UI component built with Shopify’s Checkout Extensibility framework. It lets developers and app creators safely insert custom functionality—such as product recommendations, custom fields, trust badges, and promotional banners—directly into Shopify’s one-page checkout, Thank You page, and Order Status page.
2. Can non-Plus merchants use Shopify Checkout UI Extensions?
Yes. Shopify expanded Checkout Extensibility beyond Shopify Plus for several key surfaces, including the Thank You and Order Status pages. Furthermore, pre-built checkout apps like Rivio Checkout Customizer allow eligible merchants across plans to customize their post-purchase and checkout flows using native, drag-and-drop app blocks.
3. Why did Shopify deprecate checkout.liquid in favor of Checkout Extensibility?
checkout.liquid required direct code manipulation that often broke during platform updates, introduced security risks, and caused slow page loads that harmed conversion rates. Checkout UI Extensions run in isolated sandboxes, load instantly, inherit native store styling automatically, and support accelerated checkouts like Shop Pay.
4. How do I add custom fields (like delivery instructions or gift notes) to the Shopify checkout?
You can collect custom information by building a custom UI extension that saves inputs into checkout line item properties or order metafields. Alternatively, you can use a ready-to-use app like Rivio Checkout Customizer to drag and drop custom text inputs, dropdowns, or checkbox fields without writing backend code.
5. Do Checkout UI Extensions affect store loading speed or checkout conversion?
No. Unlike older script-based customizations, Checkout UI Extensions are loaded within Shopify’s checkout architecture. Performance depends on implementation; test your extension rather than assuming zero impact.
6. Can I customize checkout without writing code or hiring a developer?
Yes. With apps built specifically for Checkout Extensibility—such as Rivio Checkout Customizer—you can add 1-click upsells, announcement banners, payment badges, and custom fields visually through the native Shopify Theme and Checkout Editor.
Was this article helpful?
Your feedback helps us continuously improve our setup guides and documentation.
Thank you for your feedback! Our documentation team reviews all suggestions.