Talk Shop
Home
Learn More
About Us
Follow Us
Blog
Tools
Newsletter
Join Discord
Join

Community

  • Developers
  • Growth
  • Entrepreneurs
  • Support
  • Experts
  • Tools

Location

123 Mars, Crater City, Red Planet

(WiFi may be spotty)

Hours

Who has time for breaks? We're here 24/7!

Contact

hello@letstalkshop.com

Talk Shop
Talk Shop

Built for real builders. Not affiliated with Shopify Inc.

Home
Privacy
Terms
  1. Home
  2. >Blog
  3. >Shopify Development
  4. >Shopify Checkout UI Extensions: A Hands-On Build Guide (2026)
Shopify Development14 min read

Shopify Checkout UI Extensions: A Hands-On Build Guide (2026)

Scaffold, build, and deploy Shopify checkout UI extensions step by step — CLI setup, targets, Polaris web components, settings, the 64KB limit, and three worked examples.

Talk Shop

Talk Shop

Jun 11, 2026

Shopify Checkout UI Extensions: A Hands-On Build Guide (2026)

In this article

  • From Reading About Extensibility to Actually Shipping Code
  • How Shopify Checkout UI Extensions Work Under the Hood
  • Scaffold Your First Extension With the Shopify CLI
  • Choose Your Extension Target
  • Build the UI With Polaris Web Components
  • Read and Write Checkout State
  • Add Merchant Settings
  • Three Mini-Builds You Can Ship Today
  • Stay Under 64KB, Then Preview and Deploy
  • Common Mistakes That Sink Checkout Extensions
  • Shopify Checkout UI Extensions FAQ
  • Ship One Extension This Week

From Reading About Extensibility to Actually Shipping Code

You have until August 26, 2026 before Shopify auto-upgrades every non-Plus store's Thank You and Order Status pages and strips whatever legacy customizations are still clinging to them. That date turns Shopify checkout UI extensions from a nice-to-know into the only sanctioned way to put custom UI back into checkout — and this guide gets you from zero to a deployed extension, with real commands and real code.

This is a build tutorial, not a concept explainer. If you want the background on why checkout.liquid died and what replaced it, read our checkout extensibility explainer first. If you need the full countdown on the deadline itself, we covered the 2026 migration deadline in depth. Here, we assume you know what extensibility is and you want to build with it.

By the end you will have scaffolded an extension with the Shopify CLI, picked the right target, rendered UI with Polaris web components, read and written checkout state, exposed merchant settings, and deployed — all while staying under the 64KB bundle limit.

What You Need Before Starting

  • Node.js 20+ and the latest Shopify CLI (npm install -g @shopify/cli)
  • A Partner account and a development store. To preview extensions on the information, shipping, and payment steps, your dev store needs the Checkout and Customer Accounts Extensibility preview (dev stores get Plus features for testing)
  • Chrome or Firefox for the Dev Console preview
  • Basic comfort with JSX — extensions render with Preact, not full React

Who This Guide Is For

Developers and technical merchants on any plan. Post-purchase surfaces — the Thank You and Order Status pages — accept checkout UI extensions on every plan except Starter, so you do not need Plus to ship something real today. We flag the Plus-only parts explicitly as we go.

How Long This Takes

Scaffolding takes five minutes. A working banner takes fifteen. A production-ready extension with settings, validation, and deploy review takes an afternoon. Budget accordingly and build the banner first — momentum matters.

How Shopify Checkout UI Extensions Work Under the Hood

A phone displaying a checkout flow next to a dark card reader terminal.

Before writing code, understand the three-part architecture, because it explains every constraint you are about to hit. Shopify checkout UI extensions consist of targets (where your UI appears), target APIs (what data you can touch), and web components (what you can render). The official checkout UI extensions reference documents all three, currently at API version 2026-04.

Sandboxed by Design

Your extension runs in an isolated web worker, completely separate from the checkout page's DOM. You never get direct access to checkout HTML, other extensions, or payment fields. That isolation is the entire point: it is why extensions survive platform upgrades and why Shopify lets third-party code anywhere near its highest-converting page.

The practical consequences:

  • No `document`, no `window` access to the host page — you describe UI, Shopify renders it
  • No arbitrary CSS or script injection — styling happens through component properties
  • Network calls require an explicit capability flag in your config file

Polaris Web Components and Remote-DOM

You build UI from Shopify-supplied web components — s-banner, s-text-field, s-stack, s-button, and dozens more — that follow the Polaris design system. Under the hood they are powered by remote-dom, Shopify's library for mirroring a component tree from your sandboxed worker into the real checkout page. The full API definitions live in the open-source Shopify/ui-extensions repository if you want to read the source.

The mental model: you compose approved building blocks; Shopify owns the rendering, the accessibility, and the visual consistency with the merchant's brand settings.

The Global shopify Object

Inside your extension, a global shopify object exposes checkout state as reactive signals — shopify.cost, shopify.lines, shopify.buyerIdentity, shopify.settings — plus methods like applyAttributeChange for writes. Reading a signal's current value uses .value; in Preact components, accessing signals makes the component re-render automatically when checkout state changes.

Scaffold Your First Extension With the Shopify CLI

Two dark monitors showing terminal output and code editor, lit with electric blue.

Everything starts with the CLI. The flow for checkout UI extensions is identical whether you are building a private customization for one store or a public app.

Create or Reuse an App

Extensions live inside an app, so create one if you do not have one:

bashbash
shopify app init
cd your-app-name

Pick the "Start by adding your first extension" template when prompted — you do not need the full Remix app scaffold for a checkout-only customization.

Generate the Extension

bashbash
shopify app generate extension --name checkout-banner

The CLI asks which extension type you want — choose Checkout UI — and which language flavor (JavaScript or TypeScript, with JSX). It then writes a new directory under extensions/.

Tour the Generated Files

texttext
extensions/checkout-banner/
├── shopify.extension.toml   # config: targets, capabilities, settings
├── src/
│   └── Checkout.jsx          # your extension code
├── locales/
│   └── en.default.json       # translatable strings
└── package.json

The two files you will live in are shopify.extension.toml (where the extension renders, what it can access, what merchants can configure) and Checkout.jsx (what it does). A minimal config looks like this:

tomltoml
api_version = "2026-04"

[[extensions]]
type = "ui_extension"
name = "Checkout banner"
handle = "checkout-banner"

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

Choose Your Extension Target

The target value is the single most consequential line in your config. It determines where your checkout UI extension can appear, which APIs you get, and which plans can use it.

Block Targets vs Static Targets

Block targets (like purchase.checkout.block.render) are merchant-positionable: the merchant drags your extension anywhere supported in the checkout editor. Use these for banners, badges, and content that does not depend on a specific form.

Static targets render at a fixed location tied to a specific piece of checkout UI — for example, purchase.checkout.delivery-address.render-after always appears directly below the delivery address form. Use these when your UI only makes sense next to one element, like a delivery note under the address fields.

The Target Map

A non-exhaustive map of the targets you will reach for most:

TargetRendersTypical Use
purchase.checkout.block.renderAnywhere the merchant places it (checkout steps)Banners, trust badges, custom fields
purchase.checkout.delivery-address.render-afterBelow the delivery addressDelivery instructions, address warnings
purchase.checkout.shipping-option-list.render-afterBelow shipping optionsDelivery date pickers, carbon-offset opt-ins
purchase.checkout.payment-option-list.render-afterBelow payment optionsFinancing explainers, payment trust copy
purchase.thank-you.block.renderThank You pageSurveys, referral offers, order FAQs
customer-account.order-status.block.renderOrder Status pageTracking info, support links, upsells

What Is Plus-Gated vs Open to All Plans

This is the question that derails most projects, so get it straight before you build. Per Shopify's checkout app extensions overview: extensions that render on the information, shipping, and payment steps are available only to stores on Shopify Plus. Extensions that appear after purchase — Thank You and Order Status pages — are available to all plans except Starter.

So a trust badge in the payment step is a Plus feature. The same badge on the Thank You page works on Basic. If you are deciding whether checkout-step customization justifies the Plus price tag, our Shopify Plus checkout customization guide walks through that math.

Build the UI With Polaris Web Components

A tablet showing a grid of glowing Polaris web components in dark mode.

Open src/Checkout.jsx. The scaffold gives you a Preact component rendered into the sandbox:

jsxjsx
import '@shopify/ui-extensions/preact';
import { render } from 'preact';

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

function Extension() {
  return (
    <s-banner heading="checkout-banner">
      <s-text>Welcome to your first extension.</s-text>
    </s-banner>
  );
}

Note the imports: @shopify/ui-extensions/preact, not React. The s- prefixed elements are the Polaris web components.

Layout Primitives

Compose structure with s-stack (vertical or inline flow with gap control), s-grid for column layouts, and s-box for padding, borders, and backgrounds. There is no custom CSS file — spacing, tone, and emphasis are all component properties, which is how Shopify guarantees your UI inherits the merchant's brand settings automatically.

Forms and Feedback

The form components mirror native checkout inputs: s-text-field, s-select, s-checkbox, s-choice-list. Feedback components include s-banner (with tone values like info, warning, critical), s-spinner, and s-icon. Every component ships with accessibility built in — labels, focus management, and screen reader support come free.

Styling Inside the System

You will be tempted to fight the design constraints. Don't. The constraint is the feature:

  • Use tone and emphasis props instead of wishing for hex codes
  • Use s-heading and s-text size variants instead of font-size overrides
  • Test against a dev store with customized branding to confirm your extension adapts

Merchants change their checkout branding without warning. Extensions that lean on the system keep looking native; anything clever breaks.

Read and Write Checkout State

A dark screen displaying syntax-highlighted JSON data structures.

Static banners are fine, but the real power of checkout UI extensions is reacting to what the buyer is doing.

Reading Cart Lines, Cost, and Buyer Identity

The shopify global exposes the live checkout as signals:

jsxjsx
function Extension() {
  // Re-renders automatically when the cart changes
  const lines = shopify.lines.value;
  const total = shopify.cost.totalAmount.value;

  const hasSubscription = lines.some(
    (line) => line.merchandise?.sellingPlan != null
  );

  if (!hasSubscription) return null;

  return (
    <s-banner tone="info" heading="Subscription in cart">
      <s-text>
        Your subscription renews automatically. Manage it anytime from
        your account. Order total: {total.amount} {total.currencyCode}
      </s-text>
    </s-banner>
  );
}

Writing Attributes and Metafields

Writes go through apply* methods that return a result you must check:

jsxjsx
const result = await shopify.applyAttributeChange({
  type: 'updateAttribute',
  key: 'gift_wrap',
  value: 'yes',
});

if (result.type === 'error') {
  console.error(result.message);
}

Cart attributes flow through to the order, so anything you write here is visible in the admin, in webhooks, and to fulfillment apps. applyMetafieldChange works the same way for structured data, and applyCartLinesChange can add or update line items — the API behind in-checkout upsells.

Intercepting the Buyer Journey

shopify.buyerJourney.intercept lets you validate before the buyer advances a step — blocking progress on a missing field, an invalid address, or a business rule:

jsxjsx
shopify.buyerJourney.intercept(({ canBlockProgress }) => {
  const age = shopify.attributes.value?.find(
    (attr) => attr.key === 'age_confirmed'
  );

  if (canBlockProgress && age?.value !== 'yes') {
    return {
      behavior: 'block',
      reason: 'Age confirmation required',
      errors: [{ message: 'Please confirm you are over 18 to continue.' }],
    };
  }
  return { behavior: 'allow' };
});

Blocking requires block_progress = true in your capabilities config, and always check canBlockProgress — on some surfaces blocking is not permitted, and your extension must degrade gracefully.

Add Merchant Settings

Hardcoded values make checkout UI extensions disposable. Settings make them products: merchants configure your extension in the checkout editor without touching code.

Define Fields in the TOML

tomltoml
[extensions.settings]

[[extensions.settings.fields]]
key = "banner_title"
type = "single_line_text_field"
name = "Banner title"
description = "Headline shown at the top of the banner"

[[extensions.settings.fields.validations]]
name = "max"
value = "80"

[[extensions.settings.fields]]
key = "show_icon"
type = "boolean"
name = "Show icon"

Supported field types include single_line_text_field, multi_line_text_field, boolean, number_integer, number_decimal, date, date_time, and variant_reference — enough to cover most configuration needs without building an admin UI.

Read Settings in Code

jsxjsx
function Extension() {
  const settings = shopify.settings.value;
  const title = settings?.banner_title ?? 'Free shipping on orders over $75';

  return <s-banner heading={title} tone="info" />;
}

Always supply a fallback — settings are empty until the merchant saves values in the editor, and your extension must render sensibly on first install.

Validate at the Edge

Use the TOML validations (min/max length, regex) to stop bad config before it reaches your code. Server-side-style validation in the extension itself is your second line of defense, not your first.

Three Mini-Builds You Can Ship Today

Theory done. Here are three complete, deployable checkout UI extensions — each one maps to a real request we see weekly from store owners.

Mini-Build 1: Custom Announcement Banner

Target: purchase.checkout.block.render. The merchant writes the message in settings; you render it. This is the "hello world" that is actually useful — shipping cutoffs, holiday notices, backorder warnings.

jsxjsx
import '@shopify/ui-extensions/preact';
import { render } from 'preact';

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

function Banner() {
  const settings = shopify.settings.value;
  const title = settings?.banner_title;
  if (!title) return null;

  return (
    <s-banner heading={title} tone={settings?.banner_tone ?? 'info'}>
      {settings?.banner_body ? <s-text>{settings.banner_body}</s-text> : null}
    </s-banner>
  );
}

Pair it with banner_title, banner_body, and banner_tone settings fields and the merchant never files a "can you change the banner text" ticket again.

Mini-Build 2: Delivery Instructions Field

Target: purchase.checkout.delivery-address.render-after (Plus-only, since it lives on a checkout step). Captures a note and writes it to an order attribute:

jsxjsx
function DeliveryInstructions() {
  const handleChange = async (event) => {
    const result = await shopify.applyAttributeChange({
      type: 'updateAttribute',
      key: 'delivery_instructions',
      value: event.currentTarget.value,
    });
    if (result.type === 'error') console.error(result.message);
  };

  return (
    <s-text-field
      label="Delivery instructions (optional)"
      details="Gate codes, safe drop-off spots, anything the driver needs."
      onChange={handleChange}
    />
  );
}

The attribute lands on the order, where your 3PL or fulfillment app picks it up. Write on onChange (fires on blur) rather than every keystroke to avoid hammering the attribute API.

Mini-Build 3: Trust Badges

Target: purchase.checkout.block.render, typically placed near the payment area (Plus) or on the Thank You page (any plan). No state needed — just disciplined layout:

jsxjsx
function TrustBadges() {
  const badges = [
    { icon: 'lock', label: 'SSL-encrypted checkout' },
    { icon: 'return', label: '30-day free returns' },
    { icon: 'delivery', label: 'Ships within 24 hours' },
  ];

  return (
    <s-stack direction="inline" gap="large" justifyContent="center">
      {badges.map((badge) => (
        <s-stack key={badge.label} direction="inline" gap="small-200" alignItems="center">
          <s-icon type={badge.icon} tone="subdued" size="small" />
          <s-text tone="subdued" size="small">{badge.label}</s-text>
        </s-stack>
      ))}
    </s-stack>
  );
}

Resist the urge to upload third-party badge images — the icon set plus subdued text reads as native trust, while an off-brand image strip reads as a banner ad.

Stay Under 64KB, Then Preview and Deploy

A dark monitor displaying a complex data visualization tree with glowing blue and green nodes.

Your compiled extension bundle cannot exceed 64KB — Shopify enforces the limit at deploy time, full stop. This surprises developers used to megabyte-scale web apps, but it is the contract that keeps checkout fast. Clear that bar, and shipping is two commands away.

The 64KB Ceiling and How to Stay Under It

Every extension on the page downloads and executes before it renders. Checkout speed is conversion-critical, and Shopify would rather reject your deploy than let a bloated bundle tax every buyer. As Ralf Elfving notes in his 20-minute checkout extension walkthrough, the constraint pushes you toward small, single-purpose extensions — which are also easier to maintain.

  • Skip heavy dependencies. No moment.js, no lodash, no UI kits — Preact and the component library are already provided by the platform
  • Check before you import. A single careless import of a large library can blow the budget instantly
  • Split unrelated features into separate extensions within the same app rather than one mega-extension
  • Inspect the build output — the CLI reports bundle size at deploy, and CI should fail loudly when you are close to the line

Size is necessary but not sufficient. Avoid waterfalls of network calls (each requires the network_access capability and adds latency), render a sensible default before remote data arrives, and never block first paint of your component on an external API. Gadget's overview of checkout UI extension architecture is a good companion read on how the sandbox model shapes performance behavior.

Local Dev Against a Dev Store

bashbash
shopify app dev

The CLI builds your extension, connects to your dev store, and hot-reloads on every save. Press p to open the Dev Console, then click your extension's preview link — it opens a checkout with your extension injected. Iterate here until the behavior is right; this loop is seconds, not minutes.

bashbash
shopify app deploy

Deploy bundles every checkout UI extension in the app, enforces the 64KB limit, and creates a new app version. For a custom app this releases to the store immediately; for a public app it goes through app review.

Place It in the Checkout Editor

Deployment makes the extension available — the merchant still has to add it. In the Shopify admin: Settings → Checkout → Customize opens the checkout editor, where block-target extensions appear in the "Apps" section and can be dragged into position, configured via your settings fields, and saved. For Thank You and Order Status placements on non-Plus stores, the same editor handles it — which is exactly the surface Shopify's upgrade guide for non-Plus stores tells merchants to migrate to before the August 26, 2026 auto-upgrade.

Common Mistakes That Sink Checkout Extensions

We see the same checkout UI extension failure patterns over and over in our Shopify developer community — most are avoidable with a checklist.

Best PracticeCommon Mistake
Verify plan gating before scoping the projectBuilding a payment-step extension for a non-Plus merchant, discovering the gate at deploy
Check canBlockProgress before blockingAssuming block always works, silently failing on surfaces where it doesn't
Provide fallbacks for empty settingsRendering undefined as the banner headline on first install
Write attributes on blur/change eventsFiring applyAttributeChange on every keystroke
Test with customized merchant brandingShipping UI that only looks right on default checkout styling
Track bundle size in CIFinding out at deploy time that a dependency added 80KB

Build-Phase Mistakes

The most expensive error is target misselection: building against purchase.checkout.block.render for a Basic-plan store, then learning checkout steps are Plus-only after the work is done. Decide the surface first, confirm the plan, then write code. The second most expensive is treating the sandbox as an obstacle — trying to smuggle in custom fonts, raw HTML, or third-party scripts. Those paths are closed by design; budget your effort inside the component system.

Launch-Phase Mistakes

Deploying is not launching. Extensions sitting un-placed in the checkout editor render nothing, and merchants frequently do not know the placement step exists. Hand off with a one-paragraph instruction (or a screen recording) showing where to drag the block and which settings to fill in. And re-test after the merchant customizes branding — your "perfect" spacing may collapse under their typography choices.

Shopify Checkout UI Extensions FAQ

Do I Need Shopify Plus to Use Checkout UI Extensions?

Only for the information, shipping, and payment steps. Thank You and Order Status page extensions work on every plan except Starter — which is exactly where the August 26, 2026 auto-upgrade pressure applies. Non-Plus merchants who currently rely on legacy thank-you-page scripts should rebuild them as extensions now.

Can I Use React Instead of Preact?

The current component model renders with Preact and Polaris web components (the s- element set). Earlier extension versions used React-style components from @shopify/ui-extensions-react; new builds on API version 2026-04 should follow the Preact + web components pattern in the current docs. Either way, your JSX knowledge transfers almost entirely.

Can an Extension Call My Own API?

Yes — declare network_access = true under [extensions.capabilities] in the TOML, then fetch as usual. Keep calls minimal and non-blocking: every external request adds latency to the most conversion-sensitive page the merchant owns.

Ship One Extension This Week

The fastest way to learn Shopify checkout UI extensions is to deploy the banner build above to a dev store today — scaffold with shopify app init, generate the extension, render an s-banner, run shopify app dev, and you will have working checkout UI before lunch. From there, layering in state, settings, and buyer-journey logic is incremental, and the August 26, 2026 deadline gives every non-Plus store a concrete reason to start now rather than later.

Keep building from here with our Shopify development tutorials and the broader payments and checkout archive on Talk Shop's blog. And when you hit the inevitable "why is my target not rendering" moment, bring it to the Talk Shop Discord dev community — there is almost always someone who shipped the same extension last month.

What is the first thing you would add to your checkout — a delivery field, an upsell, or trust badges? Join the Discord and tell us what you are building.

Shopify DevelopmentPayments & Checkout
Talk Shop

About Talk Shop

The Talk Shop team — insights from our community of Shopify developers, merchants, and experts.

Related Insights

Related

Embedded Payments: What They Mean for Ecommerce in 2026

Related

Headless CMS for Ecommerce: Choosing the Right One for Shopify (2026)

New

Business Name Generator

Generate unique, brandable business names with AI. Check domain availability instantly.

Generate Names

Talk Shop Daily

Daily ecommerce news, teardowns, and tactics.

No spam. Unsubscribe anytime. · Learn more

Try our Business Name Generator

Join the Best Ecommerce Newsletter
for DTC Brands

12-18 curated ecommerce stories from 100+ sources, delivered every morning in under 5 minutes. Trusted by 10,000+ operators.

No spam. Unsubscribe anytime. · Learn more

Join the Community

300+ Active

Connect with ecommerce founders, share wins, get feedback on your store, and access exclusive discussions.

Join Discord Server