If you've been spending time in Shopify's documentation lately, you've probably run into the term checkout UI extensions more times than you can count. It gets thrown around a lot, often alongside phrases like 'checkout extensibility' and 'Shopify Functions,' and it can start to feel like a blur of jargon pretty quickly.
Here's what it actually means in plain terms: checkout UI extensions are the official way to add custom functionality to your Shopify checkout. Whether that's an upsell widget, a gift message field, a trust badge, a custom banner, or a product add-on if it lives inside the checkout, it's almost certainly powered by a UI extension.
This guide is for merchants and developers who want to understand not just what checkout UI extensions are, but how to actually build one from scratch. We'll walk through the full process of setting up your environment, generating your first extension, writing the code, testing it, and publishing it with enough context along the way that you're not just copying commands blindly.
Let's get into it.
First, Let's Get the Terminology Straight
One of the reasons checkout UI extensions can feel confusing at first is that Shopify uses several related terms that overlap. Before we build anything, it's worth spending two minutes making sure we're talking about the same things.
Checkout extensibility is the overarching framework the new system Shopify built to replace the old checkout.liquid file. It includes several components: the Checkout Editor (a visual no-code tool), the Branding API (for styling), Shopify Functions (for business logic like discounts and shipping), and checkout UI extensions (for custom UI elements).
Checkout UI extensions are specifically the pieces that render custom interface elements at defined points in the checkout flow. They run in a sandboxed environment, inherit your brand's styling automatically, and cannot access or modify the DOM directly. This is an intentional security constraint, not a limitation; it's what makes extensions upgrade-safe and PCI-compliant.
When someone says 'I built a checkout extension,' they almost always mean a checkout UI extension built using Shopify's UI Extensions framework.
What You Can (and Cannot) Build
Before investing time into building something, it's worth knowing the boundaries. Checkout UI extensions are powerful but deliberately scoped.
You can build:
- Pre-purchase product offers and upsells
- Custom fields for capturing additional customer information (gift messages, delivery notes, VAT numbers)
- Trust badges and custom banners
- Order summary customizations
- Shipping method descriptions or icons
- Loyalty point displays and redemption interfaces
- Progress bars and free shipping thresholds
- Post-purchase surveys and offers on the thank you page
You cannot:
- Override or inject arbitrary CSS
- Render raw HTML elements like <p> or <div> you're limited to Shopify's component library
- Access sensitive payment information or the core checkout DOM
- Use browser APIs freely the sandbox restricts global web API access
- Add third-party JavaScript directly to the checkout page
If what you need falls outside these boundaries, you'll likely need Shopify Functions (for business logic) or a combination of both. But for the vast majority of checkout customization use cases upsells, custom fields, banners, trust elements UI extensions are exactly the right tool.
Plan Availability: An Important Note Before You Start
Checkout UI extensions that render on the information, custom shipping method, and payment steps are only available to Shopify Plus merchants. This is one of the most common sources of frustration for developers who set everything up, try to preview their extension, and can't see it.
Extensions that render on the thank you page and order status page, however, are available to all Shopify plans except Starter. So if you're on a standard plan and want to experiment, start with a thank you page extension.
For the purposes of this guide, we'll build an extension that works across plan levels so you can follow along regardless of what plan you're on.
What You'll Need Before Starting
Here's what needs to be in place before you write a single line of code:
1. A Shopify Partner Account
You'll build and test your extension inside a development store, which you create through the Shopify Partners program. It's free to sign up at partners.shopify.com. Once you have an account, create a development store. You don't need a paid plan for building and testing.
2. Node.js (v18 or higher)
The Shopify CLI runs on Node.js. Check what version you have by running:
node --version
If you're on anything below v18, update it. The Shopify CLI is particular about this and will throw errors if you're on an older version.
3. Shopify CLI v3.85.3 or Higher
The Shopify CLI is the command-line tool you'll use to scaffold, develop, and deploy your extension. Install it globally:
npm install -g @shopify/cli
Verify the installation:
shopify version
4. A Code Editor
VS Code is the most common choice and works well with Shopify's CLI tooling. Any editor works, but VS Code gives you the best experience for React-based extension development.
5. Basic React Knowledge
Checkout UI extensions are written in React (or vanilla JavaScript, but React is the standard and what Shopify's documentation focuses on). You don't need to be a React expert, but you should be comfortable with components, props, and hooks.
Step 1: Create Your Shopify App
Checkout UI extensions live inside Shopify apps. Even if you're not planning to distribute your extension publicly through the App Store, you still need an app as the container. Think of it as a project folder with some extra Shopify-specific configuration.
Navigate to the directory where you want to create your project and run:
shopify app init
The CLI will walk you through a few prompts. Give your app a name (something descriptive like 'my-checkout-extension' works fine for now), select 'Start by adding your first extension' when prompted, and choose your preferred language. React/JavaScript is the most common choice and has the most community examples.
Once the scaffolding is complete, navigate into your new app directory:
cd my-checkout-extension
Take a moment to look at what got created. You'll see a shopify.app.toml file at the root this is your app's configuration file. There's also a node_modules folder and a package.json. The structure should feel familiar if you've worked with any Node.js project before.
Step 2: Generate Your Extension
Now we add the actual checkout UI extension to your app. Run:
shopify app generate extension
The CLI will prompt you to choose an extension type. Select 'Checkout UI' from the list. Then it will ask for:
- Extension name: Give it something meaningful. For this guide, use 'thank-you-banner'
- Language: Choose JavaScript React
After a few seconds, the CLI will create an extensions folder inside your app directory with your new extension inside it. The structure looks like this:
extensions/
thank-you-banner/
src/
Checkout.jsx <- your main extension file
shopify.extension.toml <- extension configuration
package.json
Open shopify.extension.toml. This is where you control how your extension behaves which surface it renders on, what its name is, and any metafield access it needs. For now, it will look something like this:
api_version = '2025-01'
[[extensions]]
type = 'ui_extension'
name = 'Thank You Banner'
handle = 'thank-you-banner'
[[extensions.targeting]]
module = './src/Checkout.jsx'
target = 'purchase.thank-you.block.render'
The target field is critically important; it defines where in the checkout your extension renders. 'purchase.thank-you.block.render' puts it on the thank you page, which works on all plans. Other common targets include 'purchase.checkout.block.render' (Plus only, renders in checkout), and 'purchase.checkout.shipping-option-item.render-after' (Plus only, renders after shipping options).
Step 3: Write Your Extension Code
Open extensions/thank-you-banner/src/Checkout.jsx. The generated file will have some boilerplate, a basic React component that renders 'Hello world' or similar. Let's replace it with something more useful: a thank you banner that shows the customer a simple message with a discount code for their next order.
import {
reactExtension,
Banner,
BlockStack,
Text,
Heading,
} from '@shopify/ui-extensions-react/checkout';
export default reactExtension(
'purchase.thank-you.block.render',
() => <ThankYouBanner />
);
function ThankYouBanner() {
return (
<Banner title='Thanks for your order!' status='success'>
<BlockStack spacing='base'>
<Text>
Use code WELCOME10 on your next order for 10% off.
</Text>
</BlockStack>
</Banner>
);
}
A few things worth noting here. First, you're importing components from '@shopify/ui-extensions-react/checkout', not from React itself. These are Shopify's own UI components Banner, Text, BlockStack, Heading, Button, and many others. You cannot use arbitrary HTML elements or third-party component libraries inside an extension.
Second, you'll notice there's no CSS here. Checkout UI extensions inherit branding from the merchant's checkout settings automatically. The Banner component will adopt the merchant's color scheme, font, and styling without you needing to write a single line of CSS.
Third, reactExtension() is a wrapper function provided by Shopify that registers your React component at the specified extension target. This is the entry point for the extension.
Step 4: Access Dynamic Data
A static banner with a hardcoded message is a fine starting point, but checkout UI extensions become genuinely powerful when they respond to data about what's in the cart, who the customer is, what they ordered. Shopify exposes this through hooks.
Let's update the extension to show the customer's first name if it's available:
import {
reactExtension,
Banner,
BlockStack,
Text,
useCustomer,
} from '@shopify/ui-extensions-react/checkout';
export default reactExtension(
'purchase.thank-you.block.render',
() => <ThankYouBanner />
);
function ThankYouBanner() {
const customer = useCustomer();
const firstName = customer?.firstName || 'there';
return (
<Banner title={`Hey ${firstName}, thanks for your order!`} status='success'>
<BlockStack spacing='base'>
<Text>
Use code WELCOME10 on your next order for 10% off.
</Text>
</BlockStack>
</Banner>
);
}
The useCustomer() hook gives you access to the customer object if one is available (i.e., the buyer is logged in). Other commonly used hooks include:
- useCart() access cart lines, quantities, and product details
- useTotalAmount() get the current order total
- useShippingAddress() access the shipping address
- useApplyCartLinesChange() add or remove items from the cart
- useExtensionCapability() check what the extension is allowed to do
You can find the full list of available hooks in Shopify's UI Extensions API documentation. The range of data you have access to is genuinely broad, more than enough to build sophisticated, context-aware experiences.
Step 5: Run Your Extension Locally
Now let's see what it looks like. From your app's root directory, run:
shopify app dev
The CLI will ask you to select a development store to work with. Choose the store you created earlier. It will then start a local development server and give you a URL to preview your extension.
Here's something that trips up a lot of first-time extension builders: the extension doesn't appear automatically in your checkout just because the dev server is running. You have to add it through the Checkout Editor first.
Go to your Shopify admin, then Settings > Checkout > Customize. In the editor, navigate to the Thank You page using the dropdown at the top. Click 'Add app block' in the left panel and select your extension from the list. Once you've added it and positioned it, you'll see a live preview of your extension rendering in the editor.
Complete a test order in your development store and you'll see your extension appear on the actual thank you page.
Step 6: Test Across Scenarios
Before you ship anything, you want to make sure your extension handles edge cases gracefully. A few scenarios worth testing:
- Guest checkout: Does the extension handle the case where useCustomer() returns null? In our example, we handled this with the || 'there' fallback, but more complex extensions need similar null-safety throughout.
- Mobile viewport: The checkout editor has a mobile preview mode. Check that your extension doesn't break or look awkward on small screens.
- Shop Pay: If you're on Shopify Plus, test your extension through Shop Pay as well. You can enable this in the checkout editor's preview settings.
- Multiple extensions: If other extensions are already installed in the checkout, verify yours doesn't conflict or look out of place alongside them.
For block extension targets (which use drag-and-drop placement), you can also append ?placement-reference=THANKYOU1 to the checkout URL to test specific placement positions. This is documented in Shopify's testing guide for UI extensions.
Step 7: Deploy and Publish
Once you're happy with how the extension looks and behaves, it's time to deploy it. Run:
shopify app deploy
This bundles your extension code and pushes it to Shopify's infrastructure. After deployment, your extension code is hosted by Shopify; you don't need to manage any servers or CDN for it.
After deploying, any merchants who have installed your app (or just you, in the case of a private app) will need to activate the extension in their Checkout Editor. The placement they configured during testing will be saved, but the deployed version of the code is now live rather than the local development version.
If you're building a private extension just for your own store, this deployment flow is all you need. If you're planning to distribute through the App Store, you'll also need to submit the app through the Partners dashboard and go through Shopify's review process but that's a separate topic beyond the scope of this guide.
Common Mistakes and How to Avoid Them
After walking through a lot of extension builds, a handful of the same issues come up repeatedly. Here's what to watch for:
Using the Wrong Node Version
Shopify CLI requires Node.js 18 or higher. If you're getting cryptic CLI errors, check your Node version first. Using nvm (Node Version Manager) to switch between versions is strongly recommended if you work on multiple projects.
Forgetting to Add the Extension in the Checkout Editor
This is the single most common reason extensions don't appear during testing. The extension must be manually added and positioned in the Checkout Editor before it renders. Running shopify app dev does not automatically inject the extension into checkout.
Trying to Use Arbitrary HTML or CSS
Checkout UI extensions cannot render arbitrary HTML. If you find yourself writing <div> or <span> or trying to import a CSS file, you're going down the wrong path. Everything must use Shopify's component library. This feels restrictive at first but saves you an enormous amount of debugging time.
Not Handling Null Values from Hooks
Hooks like useCustomer() and useShippingAddress() return null when the data isn't available yet (e.g., a guest checkout or before the customer has entered their address). Always handle the null case or your extension will crash with a runtime error.
Testing on the Wrong Plan
If you're trying to add an extension to the information or payment steps and you're on a standard Shopify plan, it won't work. Those targets are Plus-only. Always double-check which targets are available for your plan.
What to Build Next
Once you've got a basic extension working, the natural next step is to build something that actually solves a real problem. Here are a few ideas to explore:
- Cart-based upsell: Use useCart() to check what's in the cart and suggest a complementary product using useApplyCartLinesChange() to add it with one click.
- Free shipping progress bar: Use useTotalAmount() to calculate how far the customer is from a free shipping threshold and display a progress indicator.
- Custom form field: Use the TextField component and useApplyMetafieldsChange() to capture a gift message and store it as a metafield on the order.
- Order protection upsell: Show a simple checkbox or button that adds a fixed-price protection product to the cart at checkout.
Each of these builds naturally on what you learned in this guide. The component library grows and the hook API is well-documented, so once you're comfortable with the basic pattern, more complex extensions are just a matter of combining the right pieces.
Frequently Asked Questions
Do I need to be a developer to use checkout UI extensions?
Not necessarily. As a merchant, you don't need to write any code to use extensions, you simply install apps that use them. Apps like CheckoutBoost are built on checkout UI extensions and expose all the functionality through a no-code interface. If you want to build your own custom extension, some coding knowledge (specifically React/JavaScript) is required.
Are checkout UI extensions the same as Shopify Functions?
No, they're different things that often work together. Shopify Functions handle business logic that runs server-side things like custom discount rules, shipping rate logic, and payment filtering. Checkout UI extensions handle what users see in the browser the visual elements, fields, and interactive components in the checkout flow. A common pattern is to pair a Shopify Function with a UI extension: the Function applies a discount, and the UI extension shows the customer why.
Can I use my own CSS to style a checkout UI extension?
No. Checkout UI extensions inherit the merchant's branding settings automatically and you cannot override or inject custom CSS. This is an intentional security and consistency constraint. The upside is that your extension will always look native to the checkout; it won't clash with the merchant's branding because it inherits it automatically.
How many extensions can I add to a checkout?
Shopify doesn't publish a hard limit, but there are practical constraints. Each extension adds weight to the checkout page, and too many can degrade performance and overwhelm customers. In practice, most stores run between two and five extensions across the checkout flow. Shopify also gives merchants control over which extensions are active, so they can disable any that aren't performing.
Do checkout UI extensions work with Shop Pay?
Yes and this is one of their key advantages over the old checkout.liquid approach. Checkout UI extensions are designed to be compatible with Shop Pay. You can preview your extension in Shop Pay mode through the Checkout Editor to verify it renders correctly.
What happens to my extension when Shopify updates its checkout?
Nothing breaks. This is the whole point of the new extensibility architecture. Your extension code sits in a separate layer from Shopify's core checkout. When Shopify ships an update, your extension code is unaffected; it continues to render at its configured extension points regardless of what changed in the underlying platform. This upgrade-safe guarantee was impossible with the old checkout.liquid approach.
Can I use third-party libraries like Lodash or Axios inside an extension?
You can bundle some third-party JavaScript libraries into your extension, but with significant limitations. The sandbox environment restricts access to many browser APIs and network requests must go through Shopify's fetch API rather than a standard HTTP client. Heavy external dependencies will also increase your bundle size, which affects checkout load time. In general, keep dependencies minimal and lean on Shopify's provided APIs wherever possible.
Is there a way to test my extension without completing a real order?
Yes. The Checkout Editor has a preview mode that lets you see how extensions look in context without completing any transaction. For more thorough testing of extension behavior (especially data from hooks), you can run shopify app dev and complete test transactions using Shopify's test payment gateway in your development store. No real money changes hands.
What's the difference between a static and a block extension target?
Static extension targets render at a fixed, predefined location in the checkout; you don't get to choose where they appear, it's determined by the target you specified. Block extension targets are more flexible: they render wherever the merchant places them using the drag-and-drop Checkout Editor. Most modern extensions use block targets because it gives merchants control over the layout. If you need your extension to appear at a very specific location that must not change, a static target is more appropriate.
Can I build a checkout UI extension that works across multiple stores?
Yes. If you publish your app through the Shopify App Store, any merchant who installs it can use your extension in their checkout. This is how apps like CheckoutBoost work: the extension is built once and then deployed to any number of merchant stores. Each merchant activates and configures it through their own Checkout Editor.
Final Thoughts
Checkout UI extensions have a bit of a learning curve upfront with the new tooling, the constrained component model, the need to work within Shopify's defined extension points rather than having free rein over the DOM. But once you've built your first one and seen how the pieces fit together, the pattern becomes clear and repeatable.
The real payoff is the reliability. Extensions built this way don't break when Shopify ships updates, they work seamlessly with Shop Pay, and they run in a security model that protects both merchants and customers. That's a meaningful improvement over the anything-goes approach of the old checkout.liquid era.
Start small. Get a basic extension working. Then iterate. The hooks API gives you enough data to build genuinely intelligent, context-aware checkout experiences upsells that respond to what's in the cart, messages that adapt based on order value, fields that capture information you actually need. That's where the interesting work happens.

