User to Cart

Shopify Checkout UI extensions: a practical guide to checkout extensibility

A Shopify Checkout UI extension is app code that adds interface to Shopify checkout at a defined target: a custom field after the address, a banner above payment methods, an upsell in the order summary, content on the Thank you page. Together with Shopify Functions, branding and web pixels, extensions make up checkout extensibility, the supported replacement for checkout.liquid. Extensions on the information, shipping and payment steps require Shopify Plus. Extensions on the Thank you and Order status pages work on every plan.

How Checkout UI extensions work

  • They live in an app. You scaffold them with Shopify CLI (shopify app generate extension) and deploy them with the app. A custom app for one store works, and so does a public app.
  • They render at targets. A target is a named location, such as purchase.checkout.block.render (placed by the merchant in the checkout editor) or purchase.checkout.delivery-address.render-before (a fixed location). Thank you page targets start with purchase.thank-you..
  • They are sandboxed. Extensions run in a web worker, not in the checkout page's DOM. They can't read card data or run arbitrary scripts on the page, and a slow or broken extension can't break checkout.
  • They use Polaris web components. From API version 2025-10 onward, extensions are written with Preact and Shopify's web components (s-text-field, s-banner, s-button and so on), and access checkout data and actions through the global shopify object.
  • Older extensions need upgrading. API version 2025-07 is the last to support React-based UI components. Shopify's documentation says extensions that aren't upgraded to Polaris web components will be blocked from updates after October 1, 2026.
  • Merchants control placement. Block targets are placed in the checkout editor, and each extension can expose settings that merchants edit there.

A complete example: a delivery instructions field

This extension adds a "Delivery instructions" field, saves the value as a cart metafield that is copied to the order, and can optionally make the field required.

Configuration

# extensions/delivery-instructions/shopify.extension.toml
api_version = "2026-07"

[[extensions]]
name = "Delivery instructions"
handle = "delivery-instructions"
type = "ui_extension"

[[extensions.targeting]]
target = "purchase.checkout.block.render"
module = "./src/Checkout.jsx"

[extensions.capabilities]
block_progress = true

[extensions.settings]
[[extensions.settings.fields]]
key = "required"
type = "boolean"
name = "Require delivery instructions"

Extension code

// extensions/delivery-instructions/src/Checkout.jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
import {useRef, useState} from 'preact/hooks';
import {useBuyerJourneyIntercept} from '@shopify/ui-extensions/checkout/preact';

export default function extension() {
  render(<Extension />, document.body);
}

function Extension() {
  const [value, setValue] = useState('');
  const timer = useRef(null);
  const required = shopify.settings.value.required === true;

  // Block progress if the merchant made the field required and it's empty.
  useBuyerJourneyIntercept(({canBlockProgress}) =>
    canBlockProgress && required && !value.trim()
      ? {
          behavior: 'block',
          reason: 'Delivery instructions missing',
          errors: [{message: 'Please add delivery instructions.'}],
        }
      : {behavior: 'allow'},
  );

  // Save to a cart metafield, debounced so we don't write on every keystroke.
  function handleInput(event) {
    const next = event.currentTarget.value;
    setValue(next);
    clearTimeout(timer.current);
    timer.current = setTimeout(() => {
      if (!shopify.instructions.value.metafields.canSetCartMetafields) return;
      shopify.applyMetafieldChange({
        type: 'updateCartMetafield',
        metafield: {
          namespace: '$app:checkout',
          key: 'delivery-instructions',
          type: 'single_line_text_field',
          value: next,
        },
      });
    }, 500);
  }

  return (
    <s-text-field
      label="Delivery instructions"
      value={value}
      onInput={handleInput}
    />
  );
}

Getting the value onto the order

Cart metafields are copied to order metafields when the order is created, provided there is a matching order metafield definition with the cart-to-order copy capability enabled. Define that order metafield in your app, and the value will appear on the order in the admin, in the GraphQL Admin API for integrations, and in notification templates.

Two limits to design around: cart metafield changes aren't available when the buyer uses an accelerated checkout such as Apple Pay or Google Pay, and merchants must allow an extension to block progress in the checkout editor. For a rule that must always hold, add a cart and checkout validation Function as well.

What else extensions can read and do

Through the shopify global, depending on the target, extensions can:

  • read the cart lines, costs, buyer identity, shipping address, delivery groups, localization and applied discounts;
  • change cart lines (for upsells), discount codes, attributes, notes and cart metafields, after checking the matching instructions;
  • read app-owned and product metafields you declare in the extension config, which is the fast way to get data such as product specs or loyalty tiers into checkout;
  • query the Storefront API (with the api_access capability);
  • call your backend (with the network_access capability, and CORS allowing any origin);
  • publish analytics events that web pixels can listen to.

Performance and UX rules

  • Prefer metafields to network calls. Write the data you need to metafields ahead of time with the Admin API, then read it in the extension.
  • Reserve space for content that loads, so checkout doesn't jump around.
  • Apply changes only when intent is clear, and debounce input. Excessive changes can get an extension rate limited.
  • Handle missing data. Buyer identity, address or delivery options may not be known yet on every step.
  • Keep copy short. Every line in checkout competes with the pay button.

Testing and rollout

  1. shopify app dev with a development store. Preview each target, including block targets at different placements.
  2. Test with accelerated checkouts, B2B buyers, multiple markets and languages, and on mobile.
  3. Deploy with shopify app deploy, then place the extension in the live store's checkout editor, which is itself the rollout switch.
  4. Watch conversion and errors after launch. Removing the block in the editor is an instant rollback.

Decision checklist

  • Which target, and is it on a Plus-only step?
  • What data does the extension need, and can it come from metafields?
  • Where does its output go: order metafield, attribute or cart line?
  • Does it need to block progress, and is a validation Function also needed?
  • Which settings should merchants control in the editor?

Frequently asked questions

What is Shopify checkout extensibility?

Checkout extensibility is Shopify's name for the supported ways to customize checkout: Checkout UI extensions for interface, Shopify Functions for backend logic, the branding settings and API for styling, and web pixels for tracking. It replaced checkout.liquid, additional scripts and script tags in checkout.

What is a Shopify checkout extension?

A Checkout UI extension is part of an app that renders UI at a defined target in checkout, the Thank you page or, as a customer account UI extension, the Order status page. It's written in JavaScript with Shopify's Polaris web components and runs in a sandboxed worker, with access to checkout data through the shopify global object.

Do Checkout UI extensions require Shopify Plus?

Extensions on the information, shipping and payment steps require Shopify Plus. Extensions on the Thank you and Order status pages are available on all plans.

Can a checkout extension call my own API?

Yes, if the extension has the network_access capability, which you request in the app's dashboard and declare in shopify.extension.toml. Your server must return permissive CORS headers because extensions run in a web worker. Where possible, prefer data prepared in metafields, which avoids a network call during checkout.

Can a checkout extension stop the buyer from continuing?

Yes. With the block_progress capability and the buyer journey intercept API, an extension can block progress and show an error, for example when a required field is empty. Merchants must allow this in the checkout editor's checkout behavior settings. For rules that must also hold for API and accelerated checkouts, use a cart and checkout validation Function.

  • Checkout

    How Shopify checkout is customized today: Checkout UI extensions, Shopify Functions, branding, and Thank you and Order status pages, with plan requirements and when a no-code app is enough.

  • Checkout customization

    How to customize the Shopify checkout page in 2026: branding in the checkout editor, custom fields, content and upsells with Checkout UI extensions, discount, delivery and payment logic with Functions, and what each option needs.

  • Shopify Functions

    What Shopify Functions are and what they can do: discount functions, cart transform, delivery and payment customization, cart and checkout validation. How they're built, plan requirements, limits, and example code.

  • Order status & Thank you page

    How to customize Shopify's Thank you and Order status pages now that additional scripts, checkout.liquid and script tags are gone: web pixels for tracking, checkout and customer account UI extensions for content, with dates and code.

  • Hire a Shopify app developer

    Freelance Shopify app developer for custom app development, ERP and accounting integrations, Checkout UI extensions and Shopify Functions. How an engagement works and what to prepare.