Life After checkout.liquid: How to Customize Shopify Checkout the Right Way
checkout.liquid is gone and your old checkout hacks went with it. Here is how Checkout UI Extensions actually work, what they can and cannot do, and how to migrate.
Marco maintains checkout for a mid-market home-goods brand doing around $9M a year on Shopify. For years his trust badges, a custom gift-note field, and a “no returns on final sale” warning all lived in a tidy block of Liquid he’d dropped into the checkout template back in 2021.
Then one morning the staging checkout rendered without any of it. No badges, no gift field, no warning.
Nothing had errored. The template just wasn’t being read anymore, because the platform had finally pulled the plug on the old checkout file his whole setup depended on. Marco called, we ran the audit, the gap was the deprecation he’d been putting off for a year.
If you’ve leaned on checkout.liquid for anything beyond the defaults, this is the moment that file stops working, and the replacement isn’t a tweak. It’s a different way of building checkout entirely, and that’s actually good news once you get past the first day of grumbling.
Why the old checkout file had to go
The thing about injecting raw Liquid and scripts into checkout is that it was always a little dangerous. Checkout is the most sensitive page in the store. It handles payment, it handles personal data, and it has to stay fast and not break across hundreds of themes and apps all reaching into the same page at once.
That free-for-all is exactly what Shopify wanted to end, because a custom script that worked fine last quarter could conflict with a new payment method, slow the page, or quietly leak data. Multiply that across every store running its own bespoke hacks and you’ve got a security and stability headache nobody can audit.
So the platform moved to Checkout Extensibility, which is a fancy name for a simple trade. You give up the ability to do anything to the page, and in exchange you get a sandboxed, supported, upgrade-safe way to do the things merchants actually need. Your customizations stop breaking every time the checkout updates, because they’re talking to a stable API instead of clinging to markup that can change underneath them.
It stings if you’ve got years of Liquid invested. But the old way was a maintenance trap, and most teams that have made the jump stop missing it within a month.
What you can and can’t touch now
This is the part worth being honest about, because the constraints are real and pretending otherwise leads to scoped work that can’t ship.
Extensions run in a sandbox, isolated from the rest of the page. You don’t get the document, you don’t write arbitrary CSS against Shopify’s markup, and you can’t drop in a random third-party script that pokes at the DOM. Instead you build with a set of approved UI components and extension targets, placing your content at defined slots in the checkout rather than anywhere you please.
Here’s roughly where the line falls:
| You can | You can’t |
|---|---|
| Add custom fields, banners, and text blocks at defined targets | Rewrite the entire checkout layout pixel by pixel |
| Show dynamic content based on cart, address, or buyer | Inject arbitrary JavaScript or external DOM scripts |
| Block checkout progress when a rule fails | Bypass Shopify’s payment or data handling |
| Offer upsells and post-purchase add-ons | Restyle native checkout elements with free-form CSS |
| Call approved external APIs you’ve declared | Reach the page outside your extension’s sandbox |
For most stores, the “can” column covers the entire wishlist. The “can’t” column mostly contains things that were fragile and risky to begin with. Where teams get stuck is a specific brand-led redesign that wanted the checkout to look like a completely different page, and even there, merchants on Shopify Plus get meaningfully more control over branding and layout than standard plans do.
So before you scope anything, the first real question is whether the store is on Plus, because that single fact decides how far the visual customization can go.
The use cases that actually come up
In practice, the requests that land on a dev’s desk cluster into a handful of patterns, and nearly all of them are well-supported.
Custom fields are the big one. A gift message, a delivery-date picker, a PO number for B2B buyers, a “leave at the door” instruction. You render the input at a checkout target and save what the buyer types as an order attribute or a metafield, so it flows through to fulfillment. This used to be a Liquid-and-prayer job. Now it’s a defined, durable pattern.
Trust and reassurance content is the next cluster. Security badges, a returns policy reminder, a shipping-cutoff banner that only appears when the buyer’s address qualifies for next-day. Conditional, dynamic, reacting to the cart and the customer. That conditional logic, showing the right message to the right person at the right step, is something the old static Liquid handled badly and extensions handle naturally.
Then there’s revenue. Checkout upsells, a last-second add-on, a “frequently bought with” nudge before payment. And validation, which is quietly one of the most valuable, blocking the order when something’s wrong. A buyer trying to ship a restricted product to a state you can’t serve, an under-minimum B2B cart, a PO box where you only ship freight. Stopping a bad order before it’s placed saves a support ticket and a refund every single time it fires.
An agency dev told us in a Slack DM, plainly: “The checkout-ui extension allows customization and addition of text blocks to the checkout page now that checkout.liquid is deprecated.” That’s the unglamorous reality. It’s not a downgrade. It’s the same jobs, done in a way that won’t silently break next quarter.
How an extension is actually put together
If you’ve built anything in React lately, the model will feel familiar fast.
An extension is a small app component. You scaffold it with the Shopify CLI, which spins up the project, the config file, and a local dev server wired to a development store. The UI is React, importing components from Shopify’s checkout library rather than raw HTML, so a banner is a <Banner> and a field is a <TextField>, and you compose those rather than styling anything into existence.
The config file is where the important decisions live. It declares your extension’s targets, the slots in checkout where your component renders, and crucially it declares any external network access you need. If your extension calls your own API to validate a discount code or fetch inventory, that endpoint has to be listed in the config, or the sandbox blocks the request. That allowlist is deliberate, it’s how the platform keeps checkout from quietly phoning home to places nobody approved.
Data flows through hooks. You read the cart, the shipping address, the buyer’s chosen options through provided APIs, and you write changes back through approved methods, applying a metafield, setting an attribute, updating a line item. Everything is mediated. You never reach past the sandbox to mutate the page directly, you ask the platform to make the change and it does it safely.
But the mental shift that trips people up isn’t the React. It’s accepting that you’re a guest in checkout now, working through a controlled interface, rather than the owner of the page doing whatever you like.
Doing things safely when the order completes
A lot of legacy checkout hacks fired logic at the moment of purchase. Send the order to an external system, trigger a fulfillment call, kick off a loyalty update. People want to know where that logic goes now.
The honest answer is that you don’t run arbitrary code “on complete” inside the sandbox the way you used to. You react to the completed order through the supported surfaces, the order-status and thank-you targets, the customer account, and most importantly your own backend listening to Shopify’s order webhooks. The reliable place for “do something when an order is placed” was never really the checkout page anyway. It’s a webhook your server subscribes to, which fires whether the buyer saw your extension or not.
So the pattern is clean if you respect it. The extension handles what the buyer sees and inputs during checkout. Your backend, triggered by the order webhook, handles what happens to the order afterward. Trying to cram post-purchase business logic into the checkout sandbox is the single most common way teams design themselves into a wall on this migration.
For validation that has to happen before the order is placed, there’s a proper tool for that too, an intercept that can stop the buyer from advancing when a condition fails, with a clear message about why. That’s the right place for “you can’t ship this here,” not a script praying it executes in time.
Testing and shipping without breaking checkout
Because checkout is the page you least want to break, the deploy story is more careful than a theme edit, and that’s a feature.
You develop against a development store with the CLI’s local server, seeing your extension render in a real checkout as you build. When it’s ready, you deploy a version through the CLI, then activate it for the store and place your blocks using the checkout editor, the visual tool where a merchant drags your extension into the slot they want. Nothing goes live just because you pushed code, someone has to position it in the editor, which gives you a clean separation between “built” and “shown to customers.”
Test the boring failure cases on purpose. What does your custom field do when it’s left empty? Does your validation actually block the order, or just show a warning the buyer can ignore? Does your banner behave on mobile, where most checkouts happen? Run a real test order end to end, all the way to a placed order, because the difference between a banner that renders and a checkout that completes is exactly the kind of thing that looks fine in preview and fails in production.
And keep an eye on the checkout editor after any app install. Other apps add their own extensions to the same slots, and ordering matters.
A migration checklist for agencies
If you’re moving a client off the old setup, the work is mostly inventory before it’s code.
Start by listing every customization currently living in checkout.liquid and the related scripts, because you can’t migrate what you haven’t catalogued. Map each one to its new home. Custom fields go to extension inputs with metafields or attributes. Trust content goes to banner and text targets. Post-purchase logic moves to order webhooks on your backend. Branding and color changes go through the checkout settings, with the honest caveat that the deep stuff needs Plus.
Anything that genuinely can’t move, a hack depending on raw DOM access or an unapproved script, gets flagged early and redesigned, not promised. It’s far better to tell a client in week one that their specific effect needs a different approach than to discover it the day before launch. Then rebuild, test every order path, stage it, and cut over with the old approach already dead.
The brands that handled this well treated the deadline as a chance to delete years of accumulated checkout cruft. Half the old hacks turned out to be things nobody needed anymore.
What we keep telling clients
The deprecation feels like Shopify taking something away, and on day one it is. You lose the ability to do anything you want to the most important page in the store. That freedom is gone and it’s not coming back.
What you get instead is checkout customization that doesn’t rot. The work you ship against the extensions API keeps working through platform updates, instead of silently dying the next time checkout changes under you. For an agency maintaining dozens of stores, that stability is worth more than the freedom ever was, because the freedom came with a maintenance bill that arrived every quarter.
The teams that struggle are the ones trying to recreate their exact old checkout pixel for pixel inside the new model. The teams that do well ask a better question, which is what the customization was actually for, and then solve that need with the supported tools. Most of the time the need is mundane and fully covered. The pixel-perfect recreation was never the point.
Marco didn’t rebuild his 2021 Liquid block. He rebuilt the three things it actually did, the trust badges, the gift-note field, and the final-sale warning, as a small extension with a validation intercept on the restricted items. It took two days, it renders on mobile where his old hack didn’t, and it’ll survive the next checkout update without him touching it. The thing he’d dreaded for a year turned out to be a cleanup he was overdue for.
Questions we get every week
Is checkout.liquid really gone, or can I get an extension? It’s deprecated and being fully retired, so treating it as gone is the safe assumption. Some stores received extended timelines, but building anything new on it is throwing away effort. Move to Checkout Extensibility now rather than betting on another extension.
Do I need Shopify Plus to use Checkout UI Extensions? No, standard plans can use extensions for fields, banners, and validation through the editor and apps. What Plus unlocks is the deeper branding and layout control, plus some advanced customization surfaces. If a client wants checkout to look dramatically different, that conversation usually ends at Plus.
Can an extension run my custom code when an order is placed? Not arbitrary code inside the checkout sandbox, no. You react to the placed order through your own backend listening to Shopify’s order webhooks, which is the reliable place for post-purchase logic anyway. Keep buyer-facing input in the extension and business logic on your server.
How long does a typical migration take? For a store with a handful of common customizations, it’s usually a few days of building and testing once the audit is done. The audit, cataloguing what exists and mapping it to new homes, is the part teams underestimate. Complex or DOM-dependent hacks add time because they need redesign, not just porting.
If your checkout still depends on the old setup and you want it rebuilt cleanly before it breaks, talk to us about your checkout migration.