Building Shopify Checkout UI Extensions: My Complete Development Guide

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:

  1. Run npm run deploy
  2. Choose “custom distribution”
  3. Install the extension on your store
  4. 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.