Shopify’s decision to sunset Ruby-based Scripts by June 30, 2026, has left many ecommerce merchants scrambling to migrate to JavaScript-based Functions. With the April 15, 2026 deadline already passed—meaning no new or edited Scripts can be published—businesses now face a tight timeline to transition or risk losing core checkout customizations.
This migration isn’t just a technical lift—it’s a fundamental shift in how discounts, shipping, and payments are handled. Unlike Scripts, which directly mutate the cart object in Ruby, Functions operate through a declarative model using WebAssembly and GraphQL inputs. Misunderstanding this difference can turn a simple migration into a months-long ordeal.
The Critical Mindset Shift: From Mutation to Declaration
The core misunderstanding in Script-to-Function migrations stems from how each model interacts with the cart. Scripts operate in a mutable environment where developers directly alter the cart’s state using Ruby methods like change_line_price. Functions, however, take a declarative approach: they receive a snapshot of the cart via GraphQL, compute changes, and return a list of operations to apply.
- Scripts: Ruby code that modifies the
Cartobject in real time, returning the altered state. - Functions: WebAssembly modules that receive a GraphQL input, process it, and return a JSON payload of operations—never touching the cart directly.
This shift from doing to declaring is where most migrations stall. Developers accustomed to Ruby’s flexibility often try to port Scripts line-by-line, only to hit walls when the underlying contract no longer exists.
The Five-Step Migration Framework (Don’t Skip Step 1)
Rushing the migration is the fastest way to miss deadlines. Our team follows a strict five-step process to ensure accuracy and efficiency:
- Audit every active Script. Open Shopify’s Script Editor and document what each Script does in plain English. Include edge cases—many merchants are surprised to find forgotten Scripts still running in production.
- Replace what can be native. Shopify’s native discount and checkout features have evolved significantly since 2016. Some Scripts no longer need migration; they can be recreated using built-in Automatic Discounts or Discount Combinations.
- Group remaining Scripts by extension point. Categorize leftover Scripts into three buckets:
- Discount logic → Discount Function
- Shipping logic → Delivery Customization Function
- Payment logic → Payment Customization Function
- Choose your toolchain. Options include:
- Gadget.dev for reduced boilerplate
- Vanilla Shopify CLI with Rust or JavaScript templates for single migrations
- Translate, test, and deploy. Never test Functions in a live environment. Use a development store with a mirror of your product catalog to validate changes before promotion.
Skipping any of these steps—especially step 1—guarantees delays. We’ve seen migrations balloon from two weeks to over three months when teams dive straight into coding without a full audit.
From Ruby Script to JavaScript Function: A Real-World Example
Consider a classic tiered discount Script that applies increasing discounts based on cart quantity. Here’s how the migration works in practice:
Original Ruby Script (Before):
class TieredDiscount
def run(cart)
quantity = cart.line_items.reduce(0) { |sum, item| sum + item.quantity }
discount_percent = case quantity
when 0..2 then 0
when 3..5 then 10
when 6..9 then 15
else 20
end
return if discount_percent == 0
cart.line_items.each do |line_item|
line_item.change_line_price(
line_item.line_price * (Decimal.new(100 - discount_percent) / 100),
message: "Tier discount (#{discount_percent}% off)"
)
end
end
end
TieredDiscount.new.run(Input.cart)
Output.cart = Input.cartMigrated JavaScript Function (After):
// src/run.js
export function run(input) {
const totalQuantity = input.cart.lines.reduce(
(sum, line) => sum + line.quantity,
0
);
const discountPercent = totalQuantity >= 10 ? 20 :
totalQuantity >= 6 ? 15 :
totalQuantity >= 3 ? 10 : 0;
if (discountPercent === 0) {
return {
discounts: [],
discountApplicationStrategy: "FIRST"
};
}
return {
discounts: [
{
message: `Tier discount (${discountPercent}% off)`,
targets: input.cart.lines.map((line) => ({
productVariant: {
id: line.merchandise.id,
quantity: line.quantity
}
})),
value: {
percentage: { value: discountPercent.toString() }
}
}
],
discountApplicationStrategy: "FIRST"
};
}Key differences emerge immediately:
- No direct mutation: The Function returns a
discountsarray instead of altering the cart directly. - Explicit targeting: Scripts used
change_line_price, while Functions require declaring which variants receive the discount and how they stack viadiscountApplicationStrategy. - GraphQL schema learning curve: Fields like
cart.line_itemsbecomecart.lines, and developers must relearn the data structure each time.
The Hidden Pitfall: Input GraphQL Queries
A common stumbling block is the input.graphql file, which defines what data the Function receives. This query is your contract with Shopify—omit a field, and your Function won’t have access to it, no matter how much logging you add.
Example Input GraphQL Query:
# src/input.graphql
query Input {
cart {
lines {
quantity
merchandise {
... on ProductVariant {
id
product {
id
tags
}
}
}
}
}
}Forget to include tags here, and your Function won’t be able to read product tags during execution. Debugging this issue often takes hours, with developers initially suspecting code errors before realizing the input query is incomplete.
Extending Beyond Discounts: Shipping and Payment Customizations
Scripts operated from a single execution point at checkout. Functions, however, split this into three distinct APIs:
- Delivery Customization Functions handle shipping options—hiding, renaming, or reordering them based on cart conditions.
- Payment Customization Functions manage payment method visibility or restrictions.
- Cart Transform Functions split or bundle line items (e.g., for product kits or components).
For example, a Script that hid Express Shipping for heavy carts becomes a Delivery Customization Function like this:
export function run(input) {
const totalGrams = input.cart.lines.reduce(
(sum, line) => sum + (line.merchandise.weight ?? 0) * line.quantity,
0
);
if (totalGrams < 20000) {
return { operations: [] };
}
const expressOption = input.cart.deliveryGroups
.flatMap(group => group.deliveryOptions)
.find(option => option.title.includes("Express"));
if (expressOption) {
return {
operations: [
{
hide: {
deliveryOption: {
id: expressOption.id
}
}
}
]
};
}
return { operations: [] };
}The key takeaway: Functions require developers to think in terms of operations rather than state mutations, a paradigm shift that demands patience and precision.
Final Thoughts: Plan Now, Migrate Smart
The June 2026 deadline isn’t just a technical milestone—it’s a deadline for action. Merchants who treat this migration as a simple code swap will face delays, while those who embrace the structural and philosophical differences between Scripts and Functions will complete the process efficiently.
Start with a full audit, leverage native Shopify features where possible, and use the migration as an opportunity to refactor outdated logic. The sooner you begin, the smoother the transition will be—before the clock runs out.
AI summary
Learn how to migrate from Shopify Scripts to Functions before June 2026 with this step-by-step guide, code comparisons, and key differences to avoid costly delays.