I have three live Plus stores running Ruby Shopify Scripts in production this week. All three lose checkout logic on June 30 2026. The Script Editor app already locked publishing on April 15 2026. The clock is loud.
TL;DR: Shopify Scripts stop firing June 30 2026. Publishing is already frozen as of April 15 2026. Migrate Line Item Scripts to Discount Functions, Payment Scripts to Payment Customization Functions, Shipping Scripts to Delivery Customization Functions. Functions are JavaScript or Rust on WebAssembly, shipped as a Shopify app via the CLI. A typical Plus store finishes in 8 weeks.
Why this matters for your store
- Every silent Script failure on July 1 2026 ships orders at full price, with the wrong payment methods, and the wrong shipping label.
- Plus merchants leaning on tag-based B2B pricing or tiered free shipping carry the heaviest migration load.
- The 60-day window from this post’s date closes the buffer. Audit week one or build a buffer week into a delayed sprint.
When exactly do Shopify Scripts stop working?
June 30 2026 is the sunset. April 15 2026 was the publish freeze. If you missed the freeze, your old Scripts still run, but you cannot deploy a new one, fix a bug, or change a threshold. Whatever shipped before April 15 is what runs until June 30.
Shopify pushed this deadline twice already, from August 2024 to August 2025 to June 2026. The platform team has confirmed the third push is the last. The Script Editor app, the Ruby runtime, and the legacy script.rb execution path are all being removed.
The failure mode is the worst kind for revenue. Scripts will not throw a checkout error. They will simply stop. Your $150 free shipping rule stops applying. Your “hide PayPal for B2B” rule stops hiding. Your free-gift threshold stops adding the gift. Customers complete checkout at full price, and your support inbox lights up Tuesday morning.
I am running three migrations this quarter. One Line Item Script (a 20% wholesale tag override), one Payment Script (hide PayPal Express for B2B), one Shipping Script (free over $150 rename). The playbook below is the order I am running them.
What replaces Shopify Scripts?
Shopify Functions replace Shopify Scripts. Functions are deterministic WebAssembly modules written in JavaScript or Rust, scaffolded with the Shopify CLI, deployed as Shopify apps, and executed at the edge during cart and checkout. There are seven Function APIs as of writing. The three that matter for Scripts are Discount, Payment Customization, and Delivery Customization.
The shape is fundamentally different. A Script mutates Input.cart in place. A Function is a query plus a result. You declare a GraphQL input query, write a pure handler that returns a result object, and Shopify applies that result to the cart. The query caches. The handler runs in WebAssembly with a hard execution budget. That is why Functions scale through BFCM where Scripts hit per-request rate limits.
| Script type | Function replacement | Plan availability |
|---|---|---|
| Line Item Script | Discount Function | All paid plans |
| Payment Script | Payment Customization Function | Plus only |
| Shipping Script | Delivery Customization Function | Plus only |
Discount Functions ship on Basic, Shopify, and Advanced. Payment and Delivery Customization stay Plus-only, mirroring the Scripts model. For broader context, the Shopify Plus checkout optimization guide covers the August 26 2026 checkout.liquid deadline that sits next to this one. The Shopify Liquid development guide is the prerequisite for the theme side.
How does a Line Item Script become a Discount Function?
A Line Item Script that mutates Input.cart.line_items becomes a Discount Function with a cart.lines query and a product_discounts result. The Ruby logic moves into a JS run handler. The discount value moves from a direct line-item mutation to a returned discounts array. The discount registers as an automatic discount in the admin so it applies without a code.
For five real Plus-store Scripts to Functions ports with full code, see 5 Shopify Scripts I migrated to Functions. For the broader Plus checkout context, the Shopify Plus checkout optimization guide covers Checkout Extensibility, Web Pixels, and the Shop Pay component.
Here is the wholesale tag override I am migrating. Before, in Ruby:
class WholesaleDiscount
def run(cart)
return unless cart.customer&.tags&.include?("wholesale")
cart.line_items.each do |li|
d = li.line_price * Decimal.new(20) / 100
li.change_line_price(li.line_price - d, message: "Wholesale 20% off")
end
end
end
WholesaleDiscount.new.run(Input.cart)
Output.cart = Input.cart
After, in JavaScript on a Discount Function (extensions/wholesale/src/run.js):
import { DiscountApplicationStrategy } from "../generated/api";
const EMPTY = { discountApplicationStrategy: DiscountApplicationStrategy.First, discounts: [] };
export function run(input) {
const tags = input.cart.buyerIdentity?.customer?.hasTags;
if (!tags?.some(t => t.tag === "wholesale" && t.hasTag)) return EMPTY;
const targets = input.cart.lines
.filter(l => l.merchandise.__typename === "ProductVariant")
.map(l => ({ productVariant: { id: l.merchandise.id } }));
if (!targets.length) return EMPTY;
return { discountApplicationStrategy: DiscountApplicationStrategy.First,
discounts: [{ targets, value: { percentage: { value: "20.0" } }, message: "Wholesale 20% off" }] };
}
Three things shifted. The customer-tag check moves into the GraphQL query (hasTags(tags: ["wholesale"])), so Shopify resolves it server-side before the handler runs. The discount applies as a percentage rather than a recomputed line price, which keeps {{ price | money }} clean across the theme. The discount appears as a discount line on the order summary rather than a mutated line price, which is cleaner for accounting and refunds.
How do Payment and Shipping Scripts map?
A Payment Script that hides, sorts, or renames payment methods becomes a Payment Customization Function with a paymentMethods query and a hide, move, or rename operation. A Shipping Script becomes a Delivery Customization Function with a cart.deliveryGroups.deliveryOptions query and the same operation set. Both are Plus-only on Scripts and Functions. The mapping is one-to-one.
The hide-PayPal-for-B2B Function is six lines of meaningful logic:
const NO_CHANGES = { operations: [] };
export function run(input) {
const tags = input.cart.buyerIdentity?.customer?.hasTags;
if (!tags?.some(t => t.tag === "b2b" && t.hasTag)) return NO_CHANGES;
const paypal = input.paymentMethods.find(pm => /paypal/i.test(pm.name));
return paypal ? { operations: [{ hide: { paymentMethodId: paypal.id } }] } : NO_CHANGES;
}
The Function executes once per cart-update event, not once per checkout render. That is a measurable performance win on Plus traffic. Scripts re-ran on every render, which is what fed the BFCM rate-limit complaints.
The free-shipping-over-$150 rename uses the same pattern with a rename operation keyed off cart.cost.subtotalAmount.amount. The threshold lives in code, version-controlled, deployed via shopify app deploy rather than pasted into the admin.
What is the 60-day migration plan?
Six steps across 60 days: audit, scope, scaffold, build, deploy, observe. The plan assumes a Plus store with one Line Item, one Payment, and one Shipping Script in production. That is the most common shape on the Plus stores I audit.
- Week 1: Audit. Pull the Scripts deprecation report from the admin (Settings, Customer events). Export every active Script. Read every
script.rb. Document the input, output, and business intent for each one. This is the work most teams skip and pay for in week 6. - Week 2: Scope. For each Script, decide build versus buy. Build a custom app when the logic references a customer tag, a metafield, or a threshold the merchant changes more than twice a year. Buy a third-party Function app for off-the-shelf rules with no custom branching.
- Weeks 3 to 5: Scaffold and build. Run
shopify app init, thenshopify app generate extension --type=functionper Function. Pick JavaScript unless your team is fluent in Rust. Write the GraphQL query first, the handler second, the unit tests third. Thefunction-runnertool takes a JSON fixture and returns deterministic output, so every business rule pins to CI before deploy. - Week 6: Deploy and register.
shopify app deployships to dev, then live. Register each Function in the admin (Discounts for Discount Functions, Settings, Payments, Customizations for Payment Functions, Settings, Shipping and delivery, Customizations for Delivery Functions). Disable the matching Script in the same maintenance window. Do not run both, because Scripts and Functions stack. - Week 7: Observe. Watch order tags, discount lines, and shipping option names for seven days. Wire a Shopify Flow trigger that pings Slack on any order with a missing customization. Compare the seven-day order log against the seven days under Scripts.
- Week 8: Buffer. Reserve the final week for the one Script that turns out messier than the audit suggested. There is always one.
Start the audit the week this post lands and you finish with buffer before June 30 2026. I have run this exact rhythm on the WD Electronics Plus engagement and the custom MSRP and Compare At pricing logic on Factory Direct Blinds. Both landed inside 14 days of dev plus a one-week observation window.
What gets harder, and what gets easier?
Functions are harder to iterate on. A Script edit was a paste in the admin. A Function edit is a code change, a CLI deploy, and an automatic-discount or customization re-registration. You also lose puts debugging, since WebAssembly has no stdout. Plan the test setup up front, run function-runner locally with a JSON fixture, and read Function logs in the partner dashboard.
The wins compound on Plus traffic. Function code lives in your repo, every change is a reviewable commit, every business rule pins to a CI fixture, WebAssembly at the edge runs faster than the Ruby interpreter Scripts used. Functions also slot into Checkout Extensibility cleanly, so they survive checkout upgrades the same way Checkout UI Extensions do. Scripts were tied to the legacy script runtime, which is the runtime being removed on June 30.
The one trap to watch is discountApplicationStrategy (First, Maximum, All). Scripts mutated prices directly, so stacking was implicit. Functions return a strategy that interacts with code-based discounts and other Function discounts. Read the strategy docs before you assume a Function discount stacks the way the Script did, especially when you have a sitewide automatic discount layered with a code.
How to verify the migration
Three checks, five minutes each:
- Place a test order with a tagged customer, a payment method that should be hidden, and a cart that should rename shipping. Confirm the order summary shows the discount line, the hidden payment method is absent, and the shipping label is renamed.
- Pull the order in the admin and inspect the discount applications. The discount should reference the Function name, not “Script.”
- Run
shopify app function runagainst the deployed Function with a JSON input fixture. Confirm the output matches the expected discount or operation list.
If any check fails, do not disable the matching Script. Roll back the Function registration, fix the handler, redeploy, retest.
The takeaway
- Audit every active Script this week, not next sprint.
- Map each Script to its Function counterpart using the one-to-one table above.
- Build custom Function apps for tag, metafield, or threshold logic.
- Ship Discount Functions first, then Payment and Delivery Customization.
- Disable Scripts only after seven days of clean Function order data.
Need help migrating Scripts to Functions before June 30? Book a free 30-minute migration audit.