If you learned Shopify checkout customization on checkout.liquid — editing Liquid templates directly, injecting jQuery through Additional Scripts, fighting the DOM to move a trust badge two pixels to the left — that entire mental model is gone. Checkout Extensibility replaced it with something structurally different: sandboxed extensions that render inside Shopify's own hosted checkout, built with a fixed set of components, deployed through Shopify CLI, and reviewed against explicit rules before they can go live. This piece is for developers who need to know, concretely, what's buildable inside that model, what's been permanently taken off the table, and where the real edges of the sandbox sit — not another explainer on why checkout conversion matters.
Why Shopify Killed checkout.liquid
checkout.liquid worked by giving developers a single editable Liquid template for the checkout page, plus an "Additional Scripts" box where you could drop arbitrary JavaScript that ran on Shopify's hosted checkout. That arrangement was flexible and it was also the source of most of Shopify checkout's stability and security problems. Any store's custom script ran in the same execution context as the actual payment flow, on a page that handles cardholder data and has to stay PCI DSS compliant. Every Shopify platform upgrade risked breaking someone's hand-rolled DOM manipulation, which meant Shopify effectively couldn't move fast on checkout without breaking merchant customizations, and merchants couldn't upgrade without re-testing every custom script against a moving target.
Checkout Extensibility is Shopify's fix: instead of merchants owning arbitrary code inside the checkout page, they build extensions against a fixed API surface — Checkout UI Extensions for buyer-facing UI, Shopify Functions for server-side business logic, and the Checkout Branding API for visual theming. Your code no longer lives inside the checkout page's execution context at all. It runs in an isolated sandbox and communicates with checkout through a mediated bridge, and Shopify calls the result "upgrade-safe" because platform updates to checkout itself don't require you to touch your extension code. That's the actual trade being made here: you give up direct DOM access and CSS control in exchange for code that Shopify can update underneath you without your store breaking on a Tuesday.
Where the checkout.liquid Deadline Actually Stands
This has been a moving target for years, so it's worth being precise about where it sits today. For Shopify Plus stores, checkout.liquid, Additional Scripts, and script tags on the Information, Shipping, and Payment steps were already unsupported, and support for the same legacy customizations on the Thank You and Order Status pages was sunset on August 28, 2025. For every non-Plus store, the equivalent cutoff for Thank You and Order Status pages lands on August 26, 2026 — meaning if you're reading this before that date, any store still running checkout.liquid on those pages needs a migration plan now, not eventually. After that date, Shopify removes the legacy customizations automatically rather than merely deprecating them.
There's a second, related deadline that trips people up because it looks similar but covers different functionality: Shopify Scripts — the Ruby-based, Plus-only mechanism for scripted discounts, shipping rate manipulation, and payment method reordering — is being retired on its own schedule, with a freeze on edits and new scripts and a final cutoff for execution by mid-2026. Scripts and checkout.liquid are different systems solving different problems, but they're both being replaced by the same two things: Checkout UI Extensions for anything buyer-facing, and Shopify Functions for anything that used to be a Script. If your store or a client's store still has either one running, the honest read is that the runway is effectively gone or nearly gone by the time you're implementing anything new — build on Extensibility from day one.
What You Can Actually Customize: The Extension Points
Checkout UI Extensions attach to specific "targets" — fixed locations Shopify exposes inside the checkout flow — and there are two flavors. Static targets render automatically in a fixed spot, like the area right after the cart line items or right after the delivery address block, and you can't reposition them. Block targets are placements a merchant drags into position themselves using the checkout editor, giving them layout control your code doesn't have. A third category, runnable targets, render nothing at all — they fire in response to checkout events (like a cart line changing) so you can react to state without putting UI on screen.
In practice this covers a real range of buildable features: custom fields collected during checkout (a gift-message box, a delivery-instructions field placed right after the address block and written to an order metafield, a "buy for someone else" toggle), banners and content blocks that react to what's in the cart, upsell and cross-sell offers rendered inline in the order summary or as post-purchase offers on the page shown after payment completes, and read/write access to a defined set of checkout state — cart lines, discount codes, gift cards, notes, custom attributes, metafields, and shipping address fields — through target APIs Shopify exposes for that purpose. Extensions are built with a component library — buttons, text fields, banners, image tiles, layout primitives — using either a React-flavored API (@shopify/ui-extensions-react) or the underlying vanilla web-components API. None of this is "add a div and style it." It's composing from a fixed component set inside a fixed set of slots.
What You Explicitly Cannot Do Anymore
This is the section every developer coming from checkout.liquid actually needs, because the constraints aren't incidental — they're the point of the redesign, and pretending otherwise sets the wrong expectations with a client or a PM. You cannot write or inject custom CSS. Every component you place inherits the merchant's brand settings from the Checkout Branding API and renders using Shopify's own styling — there is no class-name override, no inline style prop, no way to hand-tune a button's padding to match a design comp pixel-for-pixel. You cannot touch arbitrary DOM. Extensions never see the checkout page's actual HTML or its other assets; they run in an isolated JavaScript environment (a mix of iframes and Web Workers, off the main thread) and talk to checkout only through a sanitized postMessage bridge. There's no querySelector reaching out to move something the API wasn't built to expose.
You also don't get access to sensitive payment data — card fields and payment processing stay entirely outside extension reach, which is a deliberate PCI boundary, not an oversight. And you're limited to the components and global web APIs Shopify explicitly exposes; you can't drop in an arbitrary third-party JS library and expect it to run inside the sandbox the way it would on a normal webpage. The net effect: a fully custom, pixel-perfect, brand-bespoke checkout UI — the kind agencies used to build by rewriting checkout.liquid wholesale — is no longer achievable on Shopify's hosted checkout, full stop. If a client's ask is "make checkout look exactly like our Figma file," the honest answer now is that some of that ask is structurally impossible, and the Checkout Branding API's global tokens (colors, typography, corner radius, spacing, button and form styling) are the actual ceiling on visual control.
Shopify Functions: The Server-Side Half
Checkout UI Extensions control what the buyer sees. Shopify Functions control what happens to price, eligibility, and shipping — the business logic that used to live in Shopify Scripts. Functions are server-side modules (written in JavaScript/TypeScript or Rust, compiled to WebAssembly, deployed as app extensions through Shopify CLI) that receive structured cart or checkout data as input and return a defined set of operations for Shopify to execute. The categories that matter for most builds are Discount Functions (product, order, and shipping discount logic beyond what the native discount engine covers), Cart Transform Functions (bundling, line-item grouping), Delivery Customization Functions (reordering, renaming, hiding, or re-pricing shipping rates at checkout), Payment Customization Functions (hiding or reordering payment methods based on cart contents, destination, or customer attributes), and Cart and Checkout Validation Functions (blocking checkout progress and attaching an error message to a specific field when a business rule isn't met — order limits for new accounts, restricted shipping destinations, minimum order quantities). Validation Functions cap out at 25 active per store, and you choose whether checkout fails open or closed if your function throws a runtime exception, which is a decision worth making deliberately rather than by default.
The important architectural point is that Functions and UI Extensions are separate systems that compose. A "spend $75, get free shipping" banner is a UI Extension reading cart state to decide whether to render; the actual shipping rate change that makes it true is a Delivery Customization Function running server-side. Building only the visible half and assuming the backend logic will just follow is the most common mistake we see in scoped-out checkout work — they have to be designed together.
Shopify Plus vs. Standard Plans: Where the Line Actually Sits
Checkout Extensibility itself — UI Extensions, Functions, the Branding API — is available across Shopify plans, which is a genuine change from the checkout.liquid era when deep checkout customization was Plus-only. But the practical gap hasn't disappeared, it's moved. On Basic, Shopify, and Advanced plans, checkout customization is limited to what Extensibility exposes: the extension points, the branding tokens, Functions within the limits of your plan's included resources. On Plus, the checkout editor and the same Extensibility framework are available with materially higher usage ceilings and, notably, direct access to the Information, Shipping, and Payment steps of checkout for extension placement — the actual transactional pages, not just the surrounding ones. Thank You and Order Status page customization, by contrast, is available on every plan through app extensions, because those pages sit after payment completes and were never inside the PCI-scoped checkout flow to begin with.
The upshot for scoping a project: if a non-Plus client wants an upsell banner on the order summary or a custom field before the address step, that's buildable today. If they want deep interventions on the payment step itself, or usage volumes that exceed standard-plan Function limits, that conversation includes whether a Plus upgrade is actually required for the ask — worth confirming against current plan documentation before quoting, since Shopify has shifted this boundary before and will likely shift it again.
The Developer Workflow: CLI, Preview, Deploy
Extension development runs through Shopify CLI end to end. You scaffold a new checkout extension with shopify app generate extension --template checkout_ui from inside an app project, which sets up the extension's manifest, its target configuration, and a starter component tree. shopify app dev spins up a live preview against a chosen development store — if the app has a backend, CLI tunnels it locally so the extension can call it — and the preview hot-reloads as you edit, so you're looking at your extension rendering inside real checkout, not a mock. As of the 2026-04 API version, you can also write unit tests against extension logic using @shopify/ui-extensions-tester, which matters because manual click-through testing against a dev store doesn't scale once an extension has more than a couple of conditional branches.
Shipping is shopify app deploy, which builds the extension bundle and uploads it; from there it goes through Shopify's extension review before it's eligible to go live on a production checkout, the same way a public app listing would. That review step is worth planning into a project timeline — it's not instantaneous, and an extension that violates a checkout UX or content policy gets bounced back, so building against Shopify's extension guidelines from the start avoids a late surprise.
Two Worked Examples
Take a delivery-instructions field, a request that comes up constantly for D2C brands shipping to apartment buildings or handling gate codes. The build is a UI Extension targeting the placement right after the delivery address block, rendering a text field component, with its value written to a checkout note or a custom attribute on write. No backend Function is needed unless the instructions need to affect rate calculation or trigger downstream logic (routing certain instructions to a different fulfillment flow, for instance) — in which case the field write pairs with a Delivery Customization Function reading that same attribute. The entire thing lives inside supported extension points; nothing about it requires touching legacy checkout.liquid or a workaround.
Now take checkout-step upsell, the "add this $12 accessory before you pay" pattern. The visible piece is a UI Extension rendered in the order summary area (or, for the post-purchase moment, on the dedicated post-purchase offer target that fires after payment authorizes but before the order fully confirms), showing a product card and an add button that calls the cart API to append a line item. The part people underbuild is the logic layer: deciding which product to offer based on cart contents is business logic that either lives in your extension's own conditional rendering (fine for simple rules) or gets pushed to a Function if the eligibility logic needs to be consistent across multiple surfaces or needs data the UI Extension can't cheaply compute client-side. Neither example needed a custom checkout UI rewrite — both needed the right extension point picked correctly, which is most of the actual skill in this work now.
Where This Leaves Custom Checkout Work
The honest summary: Checkout Extensibility is a real constraint compared to what checkout.liquid allowed, and any developer or agency telling a client they can rebuild checkout to look like a fully custom page is either misremembering the old system or hasn't shipped against the new one. What it trades for that constraint is real too — extensions that survive Shopify's platform upgrades without a maintenance ticket every time, a PCI boundary that isn't your problem to maintain, and a component model that's actually testable and reviewable instead of a pile of Additional Scripts nobody fully understands two years later.
For most D2C brands, the extension points that exist today — custom fields, cart-aware banners, checkout and post-purchase upsells, Functions-driven discount and shipping logic, validation rules — cover the majority of what checkout customization requests actually turn out to need once you separate the real requirement from "we want it to look different." Carryup's Shopify Deep Engineering work includes exactly this: scoping checkout requests against what Extensibility actually supports, building the UI Extension and Shopify Functions pairing correctly the first time, and migrating stores still running checkout.liquid or Scripts before their support windows close. If you're weighing a checkout customization against the August 26, 2026 non-Plus deadline, or scoping something more involved than a form field, that's the conversation worth having before you start writing extension code against the wrong extension point.
If any of this sounds like your situation, talk to us. We'll tell you exactly where your revenue is leaking and what it would take to fix it. Explore Strategy & Consulting →

