On June 30, 2026, every Shopify Script stops executing. This is the final deadline — it has already been pushed twice (August 2024 → August 2025 → June 2026), and Shopify has stated this one is firm. Editing and publishing new Scripts was already turned off on April 15, 2026.
Most "deadline" posts about this frame it as a migration-project problem: audit your Scripts, rebuild them as Functions, ship before the date. That's true. But it buries the part that actually bites.
Here is the part that makes this dangerous.
When a model gets retired — Imagen, an OpenAI snapshot, a Grok slug — the failure is loud. The call returns an error, your pipeline throws, someone gets paged. Loud failures get fixed.
A Shopify Script ceasing to execute is not loud. There is no Script to call and fail. The checkout simply runs without it. The customer reaches the thank-you page. Shopify returns a completed order. Your logs show a successful checkout. Everything is 200 OK.
The only thing missing is the logic the Script was doing — and nothing in the system says so.
1. The cutoff itself fails silent — checkout just stops applying your rules
Think about what Shopify Scripts actually do. They are Ruby scripts that run inside checkout to modify three things:
- Line item discounts — "20% off for wholesale-tagged customers," "buy 3 get 1 free," tiered volume pricing.
- Shipping rates — hide express shipping for PO boxes, rename rates, discount freight for VIPs.
- Payment methods — hide credit card for high-risk carts, hide cash-on-delivery above a cart threshold.
On July 1, none of that runs. And checkout does not error — it has nothing to error about. It just renders the cart without the Script's modifications:
- The wholesale customer who should see 20% off pays full price. No error. Order completes.
- The express shipping you hid for PO boxes shows up again, gets selected, and now you owe a carrier for a route you can't service.
- The payment method you hid on high-risk carts comes back, and the fraud you were screening out walks straight through.
Every one of these is a 200, a completed order, a happy-looking checkout funnel. You will not find this in an error dashboard, because there is no error. You find it in a margin report three weeks later, or in a customer support ticket, or in a chargeback.
That is the worst kind of failure: revenue logic that silently switches off while the system around it reports success. If you have a Script live today, June 30 is not a migration deadline — it is the day a piece of your checkout silently changes behavior.
2. A deployed Function does nothing until a discount node exists in the Admin
So you migrate. You rebuild the Script as a Shopify Function — WebAssembly, the official replacement. You write the logic, npm run deploy, the Function shows up in your Partner Dashboard. Done?
No. And this is the trap that catches teams who think a Function is a drop-in for a Script.
A Script ran the moment it was saved in the Script Editor. A Function does not run just because it is deployed. A discount Function only executes when a discount is attached to it — a DiscountAutomaticApp or DiscountCodeApp node, created in the Shopify Admin (or via the discountAutomaticAppCreate GraphQL mutation) and set to Active.
Deploy the Function, forget to create the discount node — or create it and leave it in Draft, or with a future start date — and the result is exactly the same as section 1: checkout runs, completes, charges full price. The Function exists. It is just never invoked. There is no error, because nothing called anything.
This is the half-migration failure mode. The Script is gone, the Function is "deployed," the dashboard looks green, and discounts are silently off. Confirm the discount node exists, is Active, and has a start date in the past. The Function is only half the wiring.
3. The input query: a field you forget to request comes back null
Scripts and Functions receive cart data completely differently, and this is where logic silently no-ops.
A Ruby Script got the whole cart handed to it — Input.cart.line_items, customer, tags, everything in scope. A Function gets only what its input query explicitly asks for. The input query is a GraphQL document you write; Shopify runs it and passes the result to your Function as JSON.
The rule that bites: a field you do not request in the input query is null in your Function. Not an error. Not a warning. null.
So you migrate a Script that gives a discount to customers tagged wholesale. In the Function you write the discount logic correctly — but your input query doesn't request cart.buyerIdentity.customer.hasTags (or the metafield, or the product tags, or the line-item sellingPlanAllocation, whatever your rule keys on). The Function runs. It reads the customer tags. They are null. The null doesn't match wholesale. It returns "no discount."
200 OK. Order completes. Full price. The Function ran successfully — it just made its decision on data that was silently absent. Every conditional discount you migrate has this exposure: the condition you branch on must be in the input query, or your branch quietly takes the wrong path.
When you migrate a Script, write down every field its logic reads, and check each one is in the input query. The dangerous field is the one your logic depends on and your query forgot.
4. combinesWith and discount strategy: a Script that always applied may now lose
A Script applied its discount unconditionally — the Ruby ran, the discount landed, end of story. A Function discount does not get that guarantee. It lives under two layers of Shopify-side arbitration that Scripts never had:
-
combinesWithis configured on the discount node, not in your Function code. It declares whether this discount can stack with order / product / shipping discounts. If you had a Script that gave both a line-item discount and an order-level discount, and you rebuild it as two Functions whose discount nodes are not configured to combine with each other — only one applies. The other is silently dropped. Your Function code is correct; the node configuration is the bug. -
discountApplicationStrategydecides which discount wins per line item —FIRST,MAXIMUM, orALL. A Script that always stacked its discount on top may become a Function that only applies when it is the best discount on that line. On a cart that already has a better discount, yours silently does not apply.
None of this errors. The checkout completes, an order is created, a discount may even show — just not the combination your Script produced. If your Script's behavior depended on stacking, your Function's behavior now depends on the node config and the strategy matching it. Verify both against a real multi-discount cart.
One more: if you are following an older tutorial, make sure you build against the unified Discount Function API, not the legacy split Order Discount / Product Discount Function APIs — those are themselves deprecated. Migrating onto an already-deprecated target is a migration you get to do twice.
What to actually do
Inventory your Scripts now. In the Shopify admin, open the Script Editor (Apps → Script Editor) and the Shopify Scripts customizations report. List every live Script and exactly what it modifies — discount, shipping, or payment.
For each Script, decide the replacement explicitly. A Shopify Function, a public app from the App Store, or consciously dropping it. The danger is the Script nobody owns — it just stops on June 30 and no one is watching the metric it moved.
Treat "deployed" and "live" as two different states. For every discount Function: confirm the discount node exists, is Active, has a past start date, and has
combinesWithset to match the stacking your Script did.Diff the input query against the Script's logic. Every field the Ruby read must be in the Function's input query. Anything missing is
null, andnullsilently changes which branch your discount logic takes.Test on real carts before June 30, not after. Build the carts your Scripts actually targeted — the wholesale customer, the PO box address, the high-risk payment cart, the multi-discount cart — and run a full checkout. Confirm the final price, the shipping options, and the payment methods match what the Script produced. This is the only check that catches a silent no-op.
Watch the margin metric across the cutover. Average discount per order, blended shipping cost, payment-method mix. If a Script silently stops, these move before any human notices. A dashboard line is cheaper than a chargeback.
The model retirements get headlines because they fail loud. Shopify Scripts will fail quiet — checkout will keep completing, orders will keep flowing, and the only signal that something broke is a number in a report moving the wrong way. Plan for the quiet failure.
FlareCanary watches your third-party APIs and SDKs for breaking changes like this one — deprecations, response-shape changes, and silently-dropped behavior — and surfaces them before they reach production. Free tier monitors 5 endpoints.
Top comments (0)