Ezgi Hocaoğlu

Ezgi Hocaoğlu 👋

Frontend developer 🖥️ building e-commerce front-ends with Shopify, React & WordPress — 5 years of custom storefronts, widgets and checkout flows.

Ezgi Hocaoğlu — CV

PDF · one page

Preview not loading? Open the file in a new tab instead.

Currently on repeat

DISCOUNT

Hostinger

See discounted plans
Shopify article cover
Shopify
  • 8 min read

Shopify Checkout Extensions: What You Can Actually Change

For years, customising a Shopify checkout meant editing checkout.liquid and hoping nothing broke on the next platform update. That door is closed. Checkout extensibility is the supported path now, and it is a different way of thinking: instead of owning the page, you rent a few well-defined slots in it.

That constraint is the whole point. Your code cannot take the checkout down, and it keeps working when Shopify ships changes. Here is what the slots actually give you.

Where an extension can appear

An extension registers against a target — a named position in the checkout. Block targets can be placed by the merchant in the checkout editor; static targets always render in a fixed spot. The ones I reach for most often:

  • purchase.checkout.block.render — a free-floating block the merchant positions.
  • purchase.checkout.delivery-address.render-before — notes or warnings above the address form.
  • purchase.checkout.cart-line-item.render-after — per-line information, such as a lead time.
  • purchase.thank-you.block.render — the order status page, after payment.

The configuration lives in shopify.extension.toml, next to the code:

api_version = "2025-07"

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

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

  [extensions.capabilities]
  network_access = true
  api_access = true

And the extension itself is a small React component:

import {
  reactExtension,
  BlockStack,
  Banner,
  useCartLines,
  useApplyAttributeChange,
  useBuyerJourneyIntercept,
} from "@shopify/ui-extensions-react/checkout";

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

function DeliveryNote() {
  const lines = useCartLines();
  const applyAttributeChange = useApplyAttributeChange();
  const hasPreOrder = lines.some((line) => line.attributes.some((a) => a.key === "preorder"));

  useBuyerJourneyIntercept(({ canBlockProgress }) =>
    canBlockProgress && hasPreOrder && !accepted
      ? { behavior: "block", reason: "Pre-order terms not accepted",
          errors: [{ message: "Please confirm the pre-order delivery window." }] }
      : { behavior: "allow" }
  );

  if (!hasPreOrder) return null;

  return (
    <BlockStack>
      <Banner status="info" title="Part of your order is a pre-order">
        These items ship separately, within four weeks.
      </Banner>
    </BlockStack>
  );
}

What you can read, and what you can change

The API surface is deliberately narrow. You can read the cart lines, the buyer identity, the delivery address, applied discounts, shop and product metafields, and the settings the merchant filled in for your block. You can write cart and order attributes, apply metafield changes, and block progress through the buyer journey when something is not right.

What you cannot do is just as important. There is no DOM. You compose Shopify's own components, which is why an extension cannot make the checkout look broken. Pricing logic does not belong here either — discounts are the job of Shopify Functions, which run server-side and are not something a UI extension can fake.

If a requirement starts with "we need to completely redesign the checkout", the honest answer is usually branding settings plus three small extensions, not a redesign.

Where the walls are

  • Network calls need declaring. Set network_access = true and expect review; the call should be fast, because the buyer is waiting.
  • Styling is constrained. Layout comes from Shopify's components; brand-level colour and typography come from the checkout branding API, not from your CSS.
  • Some targets are Plus-only. Check the plan before you promise a placement.
  • Performance is a hard budget. An extension that waits on a slow endpoint shows the buyer a spinner at the worst moment. Render something useful first, refine after.

A short checklist before you ship

  • Does the block still make sense when the merchant moves it somewhere else in the editor?
  • What renders when your API call fails or times out?
  • Does it work on mobile, in every language the store sells in?
  • Is the buyer journey intercept reachable only when it genuinely should block?

Checkout is the most expensive place on a store to get something wrong. The narrow API is not in your way — it is what makes small changes there safe.

Filed under Shopify
All articles