Shopify’s checkout is the most critical page on any ecommerce store. It is where revenue is captured or lost. For years, Shopify Plus merchants could customise the checkout through checkout.liquid — a Liquid template that gave direct access to the checkout’s HTML. This was powerful but problematic: custom checkout code could break when Shopify updated the checkout, security vulnerabilities were common, and merchants missed out on Shopify’s ongoing checkout optimisation improvements.

Checkout Extensibility replaces this approach with a structured, sandboxed system that gives merchants meaningful customisation whilst maintaining Shopify’s ability to improve the checkout infrastructure. This guide covers the technical foundations of the new system and practical guidance for implementation.

What changed and why

The shift from checkout.liquid to Checkout Extensibility is the most significant architectural change in Shopify Plus since the platform launched. The old approach treated the checkout as a template — merchants owned the HTML and could change anything. The new approach treats the checkout as a platform — Shopify owns the HTML and provides extension points where merchants can inject custom functionality.

This matters because:

  • Security — custom checkout code can no longer access payment fields or inject arbitrary scripts.
  • Performance — extensions run in sandboxed environments that cannot block the main checkout thread.
  • Automatic upgrades — Shopify can improve the checkout (one-page checkout, Shop Pay integration, new payment methods) without breaking custom code.
  • Stability — extensions that crash are isolated and do not take down the checkout.

The trade-off is clear: you lose the ability to change anything, but you gain stability, security, and automatic access to every checkout improvement Shopify releases. For most stores, this is a significant net positive.

Comparison of checkout.liquid vs Checkout Extensibility architecture
checkout.liquid gave merchants full HTML control. Checkout Extensibility provides structured extension points that are safe, performant, and upgrade-proof.

Checkout UI Extensions

Checkout UI Extensions are React-based components that render within the checkout at specific extension points. They use Shopify’s checkout UI component library for consistent styling:

// extensions/checkout-upsell/src/Checkout.jsx
import {
  reactExtension,
  Banner,
  BlockStack,
  Text,
  Button,
  useCartLines,
  useApplyCartLinesChange,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension(
  'purchase.checkout.block.render',
  () => <CheckoutUpsell />
);

function CheckoutUpsell() {
  const cartLines = useCartLines();
  const applyCartLinesChange = useApplyCartLinesChange();
  const hasGiftWrapping = cartLines.some(
    line => line.merchandise.product.handle === 'gift-wrapping'
  );

  if (hasGiftWrapping) return null;

  const addGiftWrapping = async () => {
    await applyCartLinesChange({
      type: 'addCartLine',
      merchandiseId: 'gid://shopify/ProductVariant/123456',
      quantity: 1,
    });
  };

  return (
    <Banner title="Add gift wrapping?">
      <BlockStack spacing="tight">
        <Text>Add premium gift wrapping for £4.99</Text>
        <Button onPress={addGiftWrapping}>Add to order</Button>
      </BlockStack>
    </Banner>
  );
}

Available extension points

Checkout UI Extensions can render at dozens of locations within the checkout, including before and after the contact information section, shipping method section, payment section, and order summary. The metafields system integrates with extensions to display custom product information at checkout.

Shopify Functions for backend logic

Shopify Functions handle server-side logic that previously required Scripts. They run within Shopify’s infrastructure (not your server) and execute in under 5 milliseconds. Functions support discount logic, cart validation, payment customisation, and delivery customisation:

// extensions/volume-discount/src/run.js
// @ts-check

/**
 * @param {RunInput} input
 * @returns {FunctionRunResult}
 */
export function run(input) {
  const discounts = [];

  for (const line of input.cart.lines) {
    if (line.quantity >= 5) {
      discounts.push({
        targets: [{ cartLine: { id: line.id } }],
        value: { percentage: { value: '10.0' } },
        message: '10% volume discount (5+ items)',
      });
    } else if (line.quantity >= 10) {
      discounts.push({
        targets: [{ cartLine: { id: line.id } }],
        value: { percentage: { value: '20.0' } },
        message: '20% volume discount (10+ items)',
      });
    }
  }

  return {
    discountApplicationStrategy: 'FIRST',
    discounts,
  };
}

Post-purchase extensions

Post-purchase extensions display after payment but before the thank-you page. They are ideal for one-click upsells, survey collection, and cross-sell offers:

// Post-purchase upsell extension
import { extend, render, BlockStack, Button, Text, Image }
  from '@shopify/post-purchase-ui-extensions-react';

render('Checkout::PostPurchase::ShouldRender', async ({ inputData }) => {
  // Decide whether to show the post-purchase page
  const hasEligibleProducts = inputData.initialPurchase.lineItems
    .some(item => item.product.id === 'gid://shopify/Product/789');
  return { render: hasEligibleProducts };
});

render('Checkout::PostPurchase::Render', ({ inputData, storage }) => {
  return (
    <BlockStack spacing="loose" alignment="center">
      <Text size="large">Complete your look</Text>
      <Image source="https://cdn.shopify.com/..." />
      <Text>Add matching accessories for 25% off</Text>
      <Button
        onPress={async () => {
          await applyChangeset(inputData.token, {
            changes: [{
              type: 'add_variant',
              variantId: 456789,
              quantity: 1,
              discount: { value: 25, valueType: 'percentage' }
            }]
          });
        }}
      >
        Add for £14.99 (was £19.99)
      </Button>
    </BlockStack>
  );
});
Post-purchase upsell extension flow
Post-purchase extensions appear after payment confirmation, enabling one-click upsells without requiring customers to re-enter payment details.

Payment method customisation

Shopify Functions can hide, reorder, or rename payment methods based on cart contents, customer data, or order value. This replaces the payment customisation that previously required checkout.liquid:

// Hide Cash on Delivery for orders over £500
export function run(input) {
  const operations = [];
  const cartTotal = parseFloat(input.cart.cost.totalAmount.amount);

  if (cartTotal > 500) {
    const codMethod = input.paymentMethods.find(
      m => m.name.includes('Cash on Delivery')
    );
    if (codMethod) {
      operations.push({
        hide: { paymentMethodId: codMethod.id }
      });
    }
  }

  return { operations };
}

Delivery rate customisation

Similarly, delivery rates can be customised, renamed, or hidden based on cart contents:

// Free shipping for orders over £75
export function run(input) {
  const operations = [];
  const cartTotal = parseFloat(input.cart.cost.subtotalAmount.amount);

  for (const delivery of input.cart.deliveryGroups) {
    for (const option of delivery.deliveryOptions) {
      if (cartTotal >= 75 && option.title.includes('Standard')) {
        operations.push({
          rename: {
            deliveryOptionHandle: option.handle,
            title: 'Free Standard Delivery'
          },
          move: {
            deliveryOptionHandle: option.handle,
            index: 0  // Move to top of list
          }
        });
      }
    }
  }

  return { operations };
}

Checkout branding API

The Checkout Branding API (Plus-only) allows customisation of colours, typography, borders, and layout without checkout.liquid. Configuration happens via the GraphQL Admin API or the checkout editor in the Shopify admin.

Migrating from checkout.liquid and Scripts

Migration requires mapping each customisation to its Checkout Extensibility equivalent:

  • Custom CSS/HTML in checkout → Checkout Branding API + UI Extensions.
  • Shopify Scripts (discounts) → Shopify Functions (discount type).
  • Shopify Scripts (shipping) → Shopify Functions (delivery customisation).
  • Shopify Scripts (payment) → Shopify Functions (payment customisation).
  • Custom fields at checkout → Checkout UI Extensions with metafield storage.
  • Trust badges and content → Checkout UI Extensions at appropriate extension points.

Not everything from checkout.liquid has a 1:1 equivalent. Some highly custom modifications may need to be rethought or simplified. We handle this migration as part of our Shopify development work, including stores using the Shopify Functions framework.

Migration path from checkout.liquid to Checkout Extensibility
Migration from checkout.liquid requires mapping each customisation to its Checkout Extensibility equivalent. Some customisations need to be rethought rather than directly translated.

Testing checkout customisations

Test checkout extensions locally using the Shopify CLI development server and Bogus Gateway for test payments. The Shopify CLI provides a development preview that renders extensions within a real checkout flow:

# Start local development server for extensions
shopify app dev

# Use Bogus Gateway test credit card
# Card number: 1 (for successful payment)
# Card number: 2 (for failed payment)
# Any expiry date and CVV

Best practices and limitations

  • Keep extensions lightweight — every millisecond of extension rendering adds to checkout load time.
  • Handle errors gracefully — if your extension fails, it should fail silently rather than blocking checkout completion.
  • Test with real payment flows — use Bogus Gateway to test the full checkout including post-purchase extensions.
  • Respect the sandbox — extensions cannot access the DOM, make arbitrary network requests, or inject custom CSS outside the component library.
  • Monitor conversion impact — track checkout completion rates before and after deploying extensions to ensure they are not reducing conversions.
Checkout extension best practices summary
Checkout extensions should enhance the purchase flow without adding friction. Monitor conversion rates closely after deployment.

Checkout Extensibility represents a fundamental shift in how Shopify checkout customisation works. The transition requires investment, but the long-term benefits — automatic upgrades, better security, improved stability — make it worthwhile. If you need help migrating your checkout customisations or building new extensions, get in touch with our Shopify development team.