After diving deep into Shopify’s checkout extensibility features, I wanted to share my hands-on experience building custom checkout UI extensions. These extensions are incredibly powerful – they let you add custom functionality at specific points in the checkout flow while maintaining visual consistency with the rest of the page.
Let me walk you through everything I learned, from setup to deployment.
Key Points You’ll Learn
- How to set up and structure a checkout UI extension project
- Building custom components using Shopify’s UI library
- Integrating GraphQL queries to fetch product data
- Managing cart interactions and state updates
- Deploying extensions and configuring merchant settings
- Creating dynamic product recommendations in checkout
Important Prerequisites
Before we start, there are a few requirements you need to know about. Checkout UI extensions are currently only available for Shopify Plus customers. You also need to have checkout extensibility enabled on your store – if you’re currently using the legacy checkout system, you’ll need to follow Shopify’s upgrade guide first.
These limitations might seem restrictive, but the power and flexibility you get with checkout extensions make the upgrade worthwhile for most businesses.
Setting Up Your Extension Project
I always start by creating a new Shopify app project using the CLI:
npm create @shopify/app@latest
When prompted, I select “start by adding your first extension” and choose the checkout UI extension template. If you already have an existing app, you can add an extension by running:
npm run shopify app generate extension
The setup process will ask you to:
- Choose between TypeScript and JavaScript (I recommend TypeScript)
- Select your preferred framework (React, Vanilla JS, etc.)
- Name your extension
Understanding the Project Structure
Once the project is created, here’s what you’ll find in the extensions folder:
| File | Purpose |
|---|---|
src/Checkout.tsx |
Main component that controls UI rendering |
shopify.extension.toml |
Extension configuration and settings |
locales/ |
Translation files for multi-language support |
The shopify.extension.toml file is particularly important – it defines where your extension appears in the checkout flow and what permissions it has.
Choosing Extension Placement
One of the first decisions you’ll make is where to position your extension. Shopify offers multiple target points:
- Static targets (red in documentation): Fixed position, merchants can only enable/disable
- Dynamic targets (blue in documentation): Merchants can choose placement within a section
For my product recommendation extension, I chose purchase.checkout.cart-line-list.render-after to display it right after the cart items. This felt like the most natural placement for additional product suggestions.
[[extensions.targeting]]
target = "purchase.checkout.cart-line-list.render-after"
Building the Core Functionality
Setting Up State and Data Fetching
Here’s how I structured the main component:
interface VariantData {
title: string;
price: {
amount: string;
currencyCode: string;
};
image?: {
url: string;
altText: string;
};
product: {
title: string;
featuredImage?: {
url: string;
altText: string;
};
};
}
export default function CheckoutExtension() {
const [variantData, setVariantData] = useState<VariantData | null>(null);
const [isSelected, setIsSelected] = useState(false);
const query = useQuery();
const settings = useSettings();
const variantId = settings.selectedVariant as string;
useEffect(() => {
if (!variantId) return;
async function getVariantData() {
const queryResult = await query(`
query($id: ID!) {
node(id: $id) {
... on ProductVariant {
title
price {
amount
currencyCode
}
image {
url
altText
}
product {
title
featuredImage {
url
altText
}
}
}
}
}
`, { variables: { id: variantId } });
setVariantData(queryResult.data.node);
}
getVariantData();
}, [variantId, query]);
if (!variantData) return null;
// Component JSX here...
}
Building the UI Components
Shopify restricts you to using their predefined UI components (no raw HTML or CSS). This ensures visual consistency, but it means learning their component library. Here’s how I built the product display:
return (
<>
<Heading level={2}>Other products you may like</Heading>
<Spacer spacing="base" />
<Pressable onPress={() => setIsSelected(!isSelected)}>
<InlineLayout
columns={['auto', '80px', 'fill']}
blockAlignment="center"
spacing="base"
padding="base"
>
<Checkbox checked={isSelected} />
<Image
source={variantData.image?.url || variantData.product.featuredImage?.url}
accessibilityDescription={variantData.image?.altText || variantData.product.featuredImage?.altText}
borderRadius="base"
border="base"
borderWidth="base"
/>
<BlockStack>
<Text>{variantData.product.title}</Text>
<Text>{variantData.title}</Text>
<Text>
{variantData.price.amount} {variantData.price.currencyCode}
</Text>
</BlockStack>
</InlineLayout>
</Pressable>
</>
);
Implementing Cart Interactions
The real magic happens when connecting your extension to Shopify’s cart system:
const cartLines = useCartLines();
const applyCartLinesChange = useApplyCartLinesChange();
useEffect(() => {
if (isSelected) {
// Add product to cart
applyCartLinesChange({
type: 'addCartLine',
quantity: 1,
merchandiseId: variantId
});
} else {
// Remove product from cart
const existingLine = cartLines.find(line =>
line.merchandise.id === variantId
);
if (existingLine) {
applyCartLinesChange({
type: 'removeCartLine',
id: existingLine.id,
quantity: 1
});
}
}
}, [isSelected, variantId, cartLines, applyCartLinesChange]);
Testing and Development
During development, run npm run dev to start the development server. You’ll get a preview link that shows exactly how your extension looks in the checkout flow. The hot-reload functionality makes iterating on your design incredibly fast.
One thing I learned the hard way: always test with different product types and variants. Some products might not have images, or variants might have different pricing structures.
Deployment and Configuration
When you’re ready to make your extension permanent:
- Run
npm run deploy - Choose “custom distribution”
- Install the extension on your store
- Navigate to Settings → Checkout → Customize
In the checkout editor, you’ll see your extension listed as an app block that can be enabled and configured.
Creating Merchant Settings
To make your extension configurable, add settings to your shopify.extension.toml:
[settings]
[settings.selectedVariant]
type = "variant_reference"
name = "Selected Variant"
key = "selectedVariant"
This creates a variant picker in the checkout editor, allowing merchants to choose which product to display. The selected value becomes available through the useSettings() hook in your code.
Performance Considerations
Keep these optimization tips in mind:
- Lazy load data: Only fetch variant information when the extension renders
- Cache GraphQL queries: Use Shopify’s built-in caching mechanisms
- Minimize API calls: Batch multiple data requests when possible
- Handle loading states: Always show something while data is loading
Common Challenges and Solutions
Challenge: Extension not appearing after configuration changes
Solution: Restart your development server when modifying shopify.extension.toml
Challenge: Images not displaying correctly
Solution: Always provide fallback images and handle missing image cases
Challenge: Cart state getting out of sync
Solution: Use the useCartLines() hook to check current cart state before making changes
Practical Tips for Success
Based on my experience building several checkout extensions, here are my top recommendations:
- Start simple: Build core functionality first, then add polish
- Test extensively: Try different products, variants, and edge cases
- Follow Shopify’s design patterns: Your extension should feel native to the checkout
- Monitor performance: Extensions that slow down checkout hurt conversion rates
- Consider mobile users: Most checkout interactions happen on mobile devices
FAQ
Can I use custom CSS or external libraries?
No, you’re limited to Shopify’s UI components. This ensures consistency but requires learning their component system.
How do I handle multiple languages?
Use the translation functions provided by Shopify’s extension framework. Define translations in the locales folder and reference them in your code.
Can checkout extensions access customer data?
You have access to cart data and can request additional permissions through the extension configuration. Customer personal data access is limited for privacy reasons.
What happens if my extension has an error?
Shopify isolates extensions, so errors in your code won’t break the entire checkout process. However, your extension simply won’t render.
Is there a limit on extension file size?
Yes, extensions have size limitations. Keep your code optimized and avoid large dependencies that aren’t essential for checkout functionality.
Building checkout UI extensions opens up incredible possibilities for customizing the Shopify checkout experience. The key is starting with a clear goal, understanding the constraints, and building something that genuinely adds value for both merchants and customers.
The learning curve can be steep initially, but once you understand Shopify’s component system and extension architecture, you’ll be able to create powerful checkout customizations that integrate seamlessly with the platform.