Shopify has drawn a hard line in the sand: checkout.liquid is dead. If you're running a Shopify Plus store with custom checkout scripts, you are officially on borrowed time. The migration deadline to Checkout Extensibility has been enforced since August 2025, and any store still running legacy scripts is operating on a deprecated foundation that Shopify can break with any platform update.
This isn't a cosmetic change. It's a full architectural shift from injecting raw HTML/JS/CSS into the checkout to using a sandboxed, API-driven extension model. And if you've built anything custom in your checkout — gift with purchase logic, custom form fields, dynamic shipping rules, loyalty point redemption — every single one of those needs to be rebuilt.
We've migrated over 80 Shopify Plus checkouts at Jhango. This guide covers exactly what needs to change, the patterns we see most often, and the gotchas that catch teams off guard.
What Actually Changed (And Why Shopify Did It)
To understand the migration, you need to understand what Shopify replaced and why.
The Old World: checkout.liquid + Script Tags
Shopify Plus merchants had two primary customization tools:
- checkout.liquid — A Liquid template that gave you full control over checkout HTML. You could inject anything: custom CSS, JavaScript, tracking pixels, UI modifications, and more.
- Script Editor / Shopify Scripts — Ruby-based scripts that ran server-side to modify line items, shipping rates, and payment methods. Written in a restricted Ruby sandbox.
- Additional Scripts — Post-purchase and order status page scripts injected via the admin.
This approach was powerful but created serious problems. Checkout modifications could break Shopify's payment processing, create security vulnerabilities, slow down checkout performance, and prevent Shopify from rolling out new checkout features like Shop Pay Installments or one-page checkout.
The New World: Checkout Extensibility
Checkout Extensibility replaces all of this with a structured, component-based system:
- Checkout UI Extensions — React-based components rendered in designated extension points throughout the checkout flow
- Shopify Functions — WebAssembly-compiled logic that replaces Scripts for discounts, shipping, and payment customization
- Web Pixels — Sandboxed tracking scripts that replace raw pixel injection
- Post-Purchase Extensions — Dedicated extension points for post-purchase upsells and thank-you page customization
- Checkout Branding API — A structured API for visual customization (colors, fonts, spacing) instead of raw CSS
Shopify has already removed checkout.liquid access for new stores. Existing stores that haven't migrated risk breaking changes with every Shopify platform update. There is no extension on this — the migration is mandatory.
The 6 Most Common Customizations That Need Rebuilding
After migrating 80+ checkouts, these are the patterns we rebuild most frequently. If you have any of these, start planning now.
1. Gift With Purchase (GWP) Logic
One of the most common checkout customizations. The old approach used Shopify Scripts to automatically add a free product when cart conditions were met.
Before (Script Editor — Ruby):
# Old Script Editor approach
GIFT_PRODUCT_ID = 123456789
THRESHOLD = Money.new(cents: 100_00)
Input.cart.line_items.each do |line_item|
next unless line_item.variant.id == GIFT_PRODUCT_ID
if Input.cart.subtotal_price < THRESHOLD
line_item.change_line_price(
line_item.line_price,
message: "Spend $100 to qualify for free gift"
)
else
line_item.change_line_price(
Money.zero,
message: "Free gift with $100+ purchase!"
)
end
end
Output.cart = Input.cart
After (Shopify Function — JavaScript/Rust compiled to Wasm):
// shopify.extension.toml
[[extensions]]
type = "product_discounts"
name = "gift-with-purchase"
// src/run.js
export function run(input) {
const GIFT_VARIANT_ID = "gid://shopify/ProductVariant/123456789";
const THRESHOLD = 100.00;
const subtotal = parseFloat(
input.cart.cost.subtotalAmount.amount
);
if (subtotal < THRESHOLD) {
return { discounts: [] };
}
const giftLine = input.cart.lines.find(line =>
line.merchandise.__typename === "ProductVariant" &&
line.merchandise.id === GIFT_VARIANT_ID
);
if (!giftLine) return { discounts: [] };
return {
discounts: [{
targets: [{
productVariant: { id: GIFT_VARIANT_ID }
}],
value: {
percentage: { value: "100" }
},
message: "Free gift with $100+ purchase!"
}]
};
}
Shopify Functions run in a WebAssembly sandbox. They receive structured input (JSON), return structured output (JSON), and have strict execution time limits (max 5ms). You can't make network calls, access databases, or perform any I/O. All logic must be self-contained.
2. Custom Checkout Fields
Delivery instructions, gift messages, PO numbers, company tax IDs — any custom field you added to checkout needs to be rebuilt as a Checkout UI Extension.
Before (checkout.liquid):
<!-- Injected directly into checkout.liquid -->
<div class="custom-field-wrapper">
<label for="delivery-notes">Delivery Instructions</label>
<textarea id="delivery-notes" name="checkout[attributes][delivery_notes]"
placeholder="Gate code, leave at door, etc."></textarea>
</div>
<script>
// Save to cart attributes on change
document.getElementById('delivery-notes')
.addEventListener('change', function() {
fetch('/cart/update.js', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
attributes: { 'delivery_notes': this.value }
})
});
});
</script>
After (Checkout UI Extension — React):
// extensions/delivery-notes/src/Checkout.jsx
import {
reactExtension,
TextField,
useApplyAttributeChange,
useAttributeValues,
} from "@shopify/ui-extensions-react/checkout";
export default reactExtension(
"purchase.checkout.shipping-option-list.after.render",
() => <DeliveryNotes />
);
function DeliveryNotes() {
const applyAttributeChange = useApplyAttributeChange();
const [notes] = useAttributeValues(["delivery_notes"]);
return (
<TextField
label="Delivery Instructions"
value={notes || ""}
onChange={(value) => {
applyAttributeChange({
type: "updateAttribute",
key: "delivery_notes",
value: value,
});
}}
placeholder="Gate code, leave at door, etc."
/>
);
}
3. Dynamic Shipping Rate Modifications
If you used Scripts to hide, rename, or re-price shipping rates based on cart contents, location, or customer tags, those need to migrate to Delivery Customization Functions.
Before (Script Editor):
# Hide express shipping for heavy items
Input.shipping_rates.shipping_rates.each do |rate|
if rate.name.include?("Express")
total_weight = Input.cart.line_items.sum { |li| li.grams * li.quantity }
if total_weight > 20_000 # Over 20kg
rate.change_name("Express (Not available for heavy orders)")
rate.apply_discount(rate.price, message: "")
end
end
end
Output.shipping_rates = Input.shipping_rates
After (Delivery Customization Function):
// src/run.js
export function run(input) {
const MAX_WEIGHT_GRAMS = 20000;
const totalWeight = input.cart.lines.reduce((sum, line) => {
const weight = line.merchandise?.weight || 0;
return sum + (weight * line.quantity);
}, 0);
if (totalWeight <= MAX_WEIGHT_GRAMS) {
return { operations: [] };
}
// Hide express shipping options for heavy orders
const hideOperations = input.cart.deliveryGroups
.flatMap(group => group.deliveryOptions)
.filter(option => option.title.includes("Express"))
.map(option => ({
hide: {
deliveryOptionHandle: option.handle,
}
}));
return { operations: hideOperations };
}
4. Payment Method Filtering
Hiding or reordering payment methods based on cart value, customer group, or product type. This moves from Scripts to Payment Customization Functions.
5. Loyalty Point Redemption
If you integrated a loyalty program directly into the checkout via checkout.liquid, you'll need to rebuild it as a combination of Checkout UI Extensions (for the UI) and Shopify Functions (for applying the discount).
6. Post-Purchase Upsells
Previously done with injected scripts on the thank-you page. Now handled through dedicated Post-Purchase UI Extensions that render between payment confirmation and the thank-you page.
Migration Gotchas That Catch Teams Off Guard
Even experienced Shopify developers hit these issues during checkout migration. Here's what to watch for:
Gotcha 1: Extension Points Are Fixed
In checkout.liquid, you could place custom UI anywhere. With Checkout UI Extensions, you can only render components at designated extension points. There are roughly 20 extension points available (before/after contact info, before/after shipping, before/after payment, etc.), but you cannot create arbitrary positions.
If your current customization sits in an unusual position, you'll need to find the closest available extension point or redesign the UX entirely.
Gotcha 2: No Direct DOM Access
Checkout UI Extensions render inside a sandboxed iframe. You cannot access the parent DOM, use document.querySelector(), inject arbitrary CSS, or modify Shopify's native checkout elements. Everything must go through the Checkout UI Extensions API.
Gotcha 3: Limited Component Library
You're restricted to Shopify's component library: TextField, Checkbox, Select, Banner, Divider, BlockStack, etc. Custom HTML elements are not supported. If your current checkout has a complex interactive widget, you may need to simplify the design.
| Feature | checkout.liquid | Checkout Extensibility |
|---|---|---|
| UI Placement | Anywhere in checkout | Fixed extension points only |
| Styling | Full CSS control | Checkout Branding API only |
| JavaScript | Full browser APIs | Sandboxed, React-only |
| Server Logic | Ruby Scripts | Wasm Functions (5ms limit) |
| Tracking Pixels | Raw script injection | Web Pixels API |
| Network Calls | Full fetch/XHR | No I/O in Functions; limited in UI Extensions |
| Performance | Variable (your code quality) | Enforced (Shopify controls rendering) |
| Security | Your responsibility | Sandboxed by Shopify |
Gotcha 4: Functions Have Hard Limits
Shopify Functions compiled to WebAssembly have strict constraints:
- Execution time: Max 5ms per invocation
- Memory: Limited allocation
- No network I/O: You cannot call external APIs, databases, or services from within a Function
- Input size: The cart input object can be large for stores with many line items — write efficient parsing logic
If your current Scripts call external APIs (checking inventory from an ERP, validating against a CRM, etc.), you'll need to restructure. The common pattern is to use metafields on products/customers to pre-cache the data that Functions need.
Gotcha 5: Testing Is Different
You can't just edit a Liquid file and refresh. Checkout UI Extensions require:
- A local development setup with
shopify app dev - A development store or partner test store
- Building and deploying the extension to test on a live checkout flow
- Version management when pushing updates to production
Budget extra time for development workflow changes. Your team needs to be comfortable with Node.js, React, and the Shopify CLI.
The Migration Timeline We Recommend
Based on 80+ migrations, here's the realistic timeline for different levels of checkout complexity:
| Complexity | What's Involved | Timeline |
|---|---|---|
| Light | Basic branding, 1-2 custom fields, simple discount scripts | 2-3 weeks |
| Medium | GWP logic, shipping modifications, payment filtering, loyalty integration | 4-6 weeks |
| Heavy | Complex multi-step logic, ERP integrations, B2B workflows, post-purchase upsells, A/B testing | 8-12 weeks |
Don't try to replicate your checkout.liquid customizations 1:1. This is an opportunity to audit what actually drives conversions and drop the rest. We regularly find that 30-40% of legacy checkout customizations are either broken, unused, or actively hurting conversion rates.
Step-by-Step Migration Process
Step 1: Audit Your Current Checkout
Document every customization in your checkout.liquid file, Script Editor, and Additional Scripts. For each one, answer:
- What business problem does this solve?
- Is it still necessary? (Check analytics — is it actually being used?)
- What's the equivalent in Checkout Extensibility?
- Does it require a UI Extension, a Function, or both?
Step 2: Set Up the Development Environment
# Install Shopify CLI
npm install -g @shopify/cli @shopify/app
# Create a new app (or add to existing)
shopify app init
# Generate checkout UI extension
shopify app generate extension --type checkout_ui
# Generate a Shopify Function
shopify app generate extension --type product_discounts
# Start local development
shopify app dev
Step 3: Build and Test Extensions Individually
Migrate one customization at a time. Don't try to rebuild everything simultaneously. We typically follow this order:
- Checkout Branding — Get the visual look right first using the Checkout Branding API
- Shopify Functions — Rebuild discount, shipping, and payment logic
- UI Extensions — Add custom fields, banners, and interactive elements
- Web Pixels — Migrate tracking and analytics
- Post-Purchase — Rebuild thank-you page customizations
Step 4: Parallel Testing
Run the new Checkout Extensibility setup in parallel with your existing checkout (on a staging/development store) and compare behavior. Verify discount amounts, shipping rates, field data capture, and pixel firing.
Step 5: Deploy and Monitor
When ready, deploy the extensions to your production store and remove the legacy checkout.liquid code. Monitor checkout conversion rates, average order value, and error rates closely for the first two weeks.
When to Call In Specialists
You can handle this migration in-house if your team has experience with React, Node.js, and the Shopify CLI. But consider bringing in specialists if:
- Your checkout has more than 5 distinct customizations
- You have complex B2B logic (tiered pricing, net terms, PO numbers)
- You integrate with external systems (ERP, CRM, loyalty platforms) at checkout
- Your store does over $1M/year and checkout downtime has a real revenue impact
- Your team isn't comfortable with React or WebAssembly
"We've rebuilt checkout customizations for brands processing $50M+ annually. The investment in getting this right pays for itself in the first month through improved checkout performance and conversion rates."
Jhango's checkout team has migrated stores across fashion, beauty, food and beverage, B2B wholesale, and subscription commerce. We understand the nuances of each vertical and can identify which customizations to keep, which to drop, and which to improve during the migration.
Ready to migrate your checkout? Learn about our Checkout Extensibility services or reach out for a free checkout audit. We'll map every customization and give you a clear migration plan with timeline and costs.