Skip to content
Scripts RescueDeprecated June 30, 2026

Shopify Scripts stopped working on June 30, 2026. Here's what to do now.

If your store had a custom discount, BOGO, or bundle Script, it stopped executing on June 30, 2026, silently. No error, no alert, nothing in your logs. Here's exactly what happened, what migrating to Shopify Functions actually involves, and an honest answer on what Stackable can rebuild for you.

What actually happened
  • Shopify Scripts stopped executing on June 30, 2026, for every store still running them.
  • Any custom discount, BOGO, or bundle logic that lived inside a Script stopped applying on that date.
  • The failure is silent by design: a Shopify Function whose conditions don't match a given cart simply never fires. It doesn't throw an error, and Shopify doesn't notify you.
Why this catches merchants off guard

Merchants working through this migration describe the same danger on the Shopify community forums:

My old Script quietly stopped applying a discount and I only found out because a customer complained about being charged full price. There was no error, no alert, nothing in the order logs. I'd been running on a broken setup for weeks before I noticed.

That's the core risk of moving from Scripts to Functions: a Function is either well-formed and running, or it's misconfigured and silent. There's no visible in-between state, so a mismatched condition, a typo in a threshold, a rule scoped to the wrong collection, produces exactly the same silence as a Function working correctly. The only way to catch it is to deliberately test both directions before you trust it live.

What migrating actually involves

Four things worth knowing before you start

01

One Script often becomes three migrations

A single Script file could touch discount logic, shipping rates, and payment customization all at once. Functions split that into separate Function types, discount, delivery, and payment, each configured and deployed independently. Migrating "my Script" usually means migrating up to three different things, and this is the complaint merchants raise most often about the move.

02

No-code builders usually cover discounts and BOGO only

If your Script also touched shipping rates or payment methods, a no-code discount-Function builder, Stackable included, does not reach those. Delivery and payment customizations need a developer to write that Function directly.

03

Keep your old Script source now

Once the Script Editor is fully retired, its stored code becomes unrecoverable. Export or copy your old Script source today, even if you are not ready to migrate yet, so you have a reference for whatever replaces it.

04

Test a should-fire cart and a should-not-fire cart

Before trusting a new Function, build two test carts: one that should trigger it, and one that deliberately should not. A Function that fires when it should not is just as costly as one that silently never fires.

How Scripts and Functions actually work

The three things that explain every migration decision

Almost every argument about what can and cannot be rebuilt comes down to three facts: Scripts came in three types with one slot each, Functions split those three types across four different APIs, and a Function has to declare every piece of data it will ever read before it runs. Once you have those, the rest of this page is arithmetic.

Shopify Scripts came in three types

The Script Editor asked you to pick a type before you wrote a line of Ruby, and the type decided what your code was allowed to touch.

Line item scripts

The ones that changed what products cost. They walked the cart line by line and repriced it, which is where every tiered discount, BOGO, bundle price and markdown lived.

Shipping scripts

The ones that worked on delivery rates. They could discount a rate, hide it, rename it, or reorder the list the shopper saw. Only the first of those four is a discount.

Payment scripts

The ones that worked on payment methods, with the same four verbs: hide a method, rename it, reorder the list, or in practice mostly hide it from certain carts.

Now the constraint that shaped every real Script you will ever read: only one Script of each type could be published at a time. One line item slot for the whole store. So a merchant running a VIP discount, a clearance price, a spend threshold and a seasonal promotion did not write four Scripts. They wrote one file with all four merged into it, usually with a hand-rolled "keep whichever gives the better price" rule at the bottom. That is why real Scripts look tangled. They are not one rule badly written, they are several rules that were never allowed to be separate.

See a real Script with four campaigns merged into one file

Four Function APIs replaced them

Shopify did not replace Scripts with one thing. It replaced them with four, split by what the code is allowed to change rather than by where in the cart it runs.

Discount Function API

Money off. Product discounts, order discounts and shipping discounts all live here now, in one unified API. If your Script changed a price, this is where it goes.

Shopify's documentation

Delivery Customization API

Hiding, renaming and reordering delivery options. Shopify is explicit that this is the only API that can customize delivery options at checkout, so a discount app cannot reach it however it is built.

Shopify's documentation

Payment Customization API

Hiding, renaming and reordering payment methods, plus payment terms and whether an order needs review. The payment half of the old Script Editor, in one place.

Shopify's documentation

Cart and Checkout Validation API

Blocking checkout with a message. Purchase limits, age checks and minimum order rules land here. It returns errors, so it can stop an order but it cannot quietly change one.

Shopify's documentation

Stackable is a Discount Function app, and only that. We build discount functions, so we can rebuild anything your Script did to a price, including shipping discounts. We do not ship a delivery customization, a payment customization or a validation function, so hiding a shipping rate, renaming a payment method or capping a purchase quantity is not something we decline for want of building it. It is a different kind of app.

Put the two halves together and you get the sentence merchants find out the hard way: one Script often becomes three migrations. A line item Script that also hid a shipping rate and blocked a payment method is now a discount function, a delivery customization and a payment customization, written and deployed separately. We would rather you read that here than discover it after the rebuild.

See a shipping Script that turns out not to be a discount at all

Functions are pure, and every field is declared in advance

A Script ran as ordinary Ruby against whatever the cart happened to hold. A Function runs sandboxed, and Shopify guarantees it has none of the following:

  • No network. A Function cannot call your ERP, your loyalty provider or any other service while a shopper is checking out.
  • No clock. A Function cannot ask what time it is, so "half price on Tuesdays" has to become a scheduled discount rather than a condition inside the code.
  • No randomness. Nothing can roll a dice for one shopper in ten.
  • No filesystem, and no reaching for a field it did not ask for. Every piece of data a Function reads has to be named in advance, in an input query, and that query has a hard cost ceiling set by Shopify.

Shopify's documentation

That last rule is the one that costs merchants the most, and it is worth being precise about who it costs. It is not that Shopify hides the data. Our own checkout query is already sitting at the maximum cost Shopify allows, so asking for one more field means giving up another. Nine of the Script shapes we refuse today are refused for exactly that reason and nothing else, including skipping items that are already marked down, reading a province or postal code, and checking how many orders a customer has placed. Shopify offers every one of those. We have not made room for them yet. That is our gap, and calling it a platform limit would be a lie.

Only two refusals in the whole library are genuine Shopify limits, and both come from one fact: every discount function on a store runs at the same moment, and none of them can see what the others did. So a Script that asked "has this item already been discounted?" or measured a threshold against an already-reduced subtotal cannot be rebuilt by us or by anyone else, today, at any price. Everything else we refuse is ours to fix, and it is labelled that way in the app and on every page of the library.

Read the two refusals that really are Shopify limits
The difference that costs the most

Setting a price is not the same as taking money off

If you read only one thing before rebuilding a Script by hand, read this one. Two files can call the same method, with the same constant and the same message, and mean opposite things. Both of the following are real, both are common, and the difference between them is a subtraction sign.

This SETS the price to $9.99

FIXED_PRICE = Money.new(cents: 999)

Input.cart.line_items.each do |line_item|
  next unless line_item.variant.product.product_type == "Clearance"
  line_item.change_line_price(FIXED_PRICE * line_item.quantity, message: "Clearance $9.99 each")
end

Output.cart = Input.cart

On a $79.00 item, the discount is $69.01 and the shopper pays $9.99. Rebuilt as a volume offer whose reward is a set price, counted per variant and repeating, so every unit is repriced.

The worked example, with the cart and the settings

This takes $9.99 OFF

DISCOUNT = Money.new(cents: 999)

Input.cart.line_items.each do |line_item|
  next unless line_item.variant.product.product_type == "Clearance"
  new_price = line_item.line_price - (DISCOUNT * line_item.quantity)
  line_item.change_line_price(new_price, message: "$9.99 off each")
end

Output.cart = Input.cart

On the same $79.00 item, the discount is $9.99 and the shopper pays $69.01. Rebuilt as a volume offer whose reward is an amount off each unit, counted per product and firing once.

The worked example, with the cart and the settings

Same method, same $9.99, same wording in the message. The only tell is that the second file subtracts from `line_item.line_price` before it assigns. Read it the wrong way round on a $79.00 item and you are out by $59.02 on a single unit, in whichever direction hurts. And the two rebuilds do not just differ in the number: one counts per variant and repeats, the other counts per product and fires once, so they also diverge as soon as the shopper buys two.

This is the failure the usual advice cannot catch. A rebuilt offer that reads the file backwards still fires on a cart that should trigger it and still stays quiet on a cart that should not, so testing both directions passes it through. Scripts Rescue closes that by asking a different question: before it will let you publish, it wants the amount your old Script charged for a cart you can still reproduce, and it compares that to what the rebuilt offer computes. One cent apart and publishing stays shut. Across the five apps selling Scripts migration today, our research found none that asks a merchant to prove the money at all.

Translating the vocabulary

What each piece of Ruby becomes in a campaign

A rebuild goes wrong in the same few places every time, and they are almost never the obvious ones. The reward is usually easy. Counting and recurrence are where the money quietly moves, because a Script says them in arithmetic and a campaign says them in a setting.

In your ScriptIn a campaignWhere it goes wrong
change_line_price(PRICE * qty)A volume offer with a set-price reward.Assignment, not subtraction. This is the trap above. The unit price becomes the constant, so a $79 item sells for $9.99.
line_price - (AMOUNT * qty)A volume offer with an amount-off-each reward.The subtraction is the whole signal. Same constant as the row above, a completely different bill.
line_price * 0.9A percentage reward of 10.The multiplier is what the shopper keeps, not what they save. Typed straight in as 90 it gives away nine tenths of the catalogue.
next unless line_item.quantity >= 3A tier with a minimum quantity of 3.Tiers are inclusive at the boundary and only the highest satisfied tier fires. Scripts that added one rule on top of another have no single-offer equivalent, and need one offer each.
line_items.size > 1A minimum quantity of 2.Not the same rule. Scripts counted separate cart lines here, campaigns count units, so one line holding two of the same item now qualifies where the Script ignored it.
sets = quantity / (BUY + GET)Buy X Get Y with the free units counted as additional.Dividing by BUY alone instead makes the free units inclusive. On nine units of a buy 3 get 1, that is two free or three free. One character apart, half again as much given away.
customer.tags.include?("vip")Customer eligibility scoped to tags.Shopify matches tags exactly, capital letters included, while a lot of Script dialects lowercased both sides first. Check the spelling before publishing, not after.
product.tags / product_type / vendorTarget scope set to tags, product types or vendors.A missed scope line is the most expensive mistake on this table, because it turns "20% off the sale tag" into 20% off everything you sell.

One thing you will not find in this table is collections. A Script could read a product's tags, type and vendor but never its collections, so any Ruby claiming to check a collection was not doing what it looked like it was doing. Campaigns can target collections; your old Script could not.

Every shape, written out

Look up your Script instead of reasoning about it

Every Script shape we have catalogued has its own page: the original Ruby, what it is recognised as, the exact configuration it becomes, a cart from our public demo store with the discount the real engine computes for it, and the cart that has to stay quiet. The refusals are written out the same way, each one labelled as a Shopify limit, our own gap, or a job for a different kind of app. It is the reference we wanted when we started, so it is public.

Script shapes
42
Rebuilt today
38
Refused, with a reason
21
Where Stackable fits, and where it doesn't

An honest answer on scope

Stackable rebuilds rule-based discount logic on native Shopify Functions. It does not run arbitrary custom code.

Stackable rebuilds this

  • Tiered / volume discounts (e.g. "buy 3+, save 15%")
  • Buy X Get Y and BOGO logic, including repeat tiers
  • Explicit discount stacking rules between offers
  • Scheduled start, end, and instant pause on any campaign

You'll still need a developer for this

  • Arbitrary custom code your old Script ran (bespoke pricing formulas, one-off conditions no builder covers)
  • Shipping / delivery customizations (a separate Function type)
  • Payment customizations (a separate Function type)
  • Anything that does not reduce to a rule-based discount configuration

If your old Script did something in this list, a no-code builder, Stackable included, can't reach it. That's a developer-written Function, not a config screen.

FAQ

Common questions

Are my old Scripts gone?

Scripts stopped executing on June 30, 2026, but that isn't necessarily the same moment the Script Editor's stored source is deleted. Export or copy your old Script code now regardless: once Shopify fully retires the editor, it becomes unrecoverable.

Will I get an error if a Function is misconfigured?

No. A Function whose conditions don't match a cart simply never fires, silently, with no error and no alert. Always test with a cart that should trigger it and one that should not before you trust it in production.

Does Stackable replace everything my old Script did?

Only the rule-based part: tiered and volume discounts, BOGO, discount stacking, and scheduling, all rebuilt on native Shopify Functions. Stackable does not run arbitrary custom code. If your Script did something genuinely bespoke, a custom pricing formula or a condition no builder covers, that still needs a developer to write the Function directly.

What about the shipping or payment logic my Script handled?

Those are separate Function types (delivery and payment) that Stackable does not touch. A developer needs to migrate those independently of your discount logic.

Which plan is Scripts Rescue on?

Scripts Rescue, the tool that reads your old Ruby and rebuilds it, is on the Pro plan. Stackable itself is free to install and the core discount engine, including volume tiers, Buy X Get Y, spend goals, free shipping, scheduling and the simulator, is free on every plan. The Ruby rescue specifically is not: it sits with multi-store and Shopify Flow on Pro.

How do I know the rebuilt discount charges what the old one did?

Because you are made to prove it before you can publish. Testing a cart that should fire and one that should not is the standard advice, and it does not catch the worst failure, which is an offer that fires correctly for the wrong amount. Scripts Rescue asks you for the amount your old Script charged on a cart you can still reproduce, compares it to what the rebuilt offer computes, and keeps publishing shut until the two match to the cent. Our research across the five apps selling Scripts migration found none that asks for that.

Why did my Script look so complicated?

Because only one Script of each type could be published at a time. A store running four promotions could not write four Scripts, so it wrote one file with all four merged together and a rule at the bottom deciding which one won. Most Ruby that looks unreadable is really several simple rules sharing a slot, and each of them usually rebuilds cleanly on its own.

Where can I read the full migration write-up?

Our first blog post walks through the Scripts deprecation and the migration path in more detail, and the Script examples library has a page per Script shape with the original Ruby and the exact rebuild.

Install Stackable and rebuild your rule-based discounts

Volume tiers, BOGO, stacking, and scheduling, running on native Shopify Functions from day one.

Scripts Rescue is on the Pro plan. Stackable itself is free to install, with no card required.

We use essential cookies to run this site, and, only with your permission, analytics cookies to understand traffic. Read our Cookie Policy.