What broke on 30 June 2026
Scripts came in three types, and it is worth knowing which one you are missing. All three stopped on the same day. Most stores had more than one, and most stores have forgotten at least one of them.
- Line item scripts — percentage and fixed discounts on products, tiered pricing, buy-three-get-one, customer-tag gating, wholesale rates.
- Shipping scripts — free shipping thresholds, rate renaming, hiding express options for certain postcodes or product types.
- Payment scripts — hiding or reordering gateways, blocking cash on delivery over a value, restricting purchase orders to tagged accounts.
Why your discounts stopped applying without an error
This is the part that makes it a forensic problem rather than an outage.
Nothing errored. Checkout still completes, orders still process, payment still captures. The discount simply is not applied, and the customer pays full price. There is no error log, no failed webhook, no alert.
So the first signal is almost never technical. It is a customer emailing to ask why their trade discount did not come off. It is a wholesale account querying an invoice. It is a margin number that looks slightly too good in a monthly report. By the time anyone connects that to a platform deadline, the store has been quietly charging the wrong prices for weeks — and if the script was a shipping subsidy rather than a discount, it has been charging customers for delivery that was meant to be free.
Script Editor is read-only, and the source may already be gone
Editing and publishing stopped on 15 April 2026. Execution stopped on 30 June. Shopify then kept the Script Editor app open in read-only mode so merchants could refer back to their original logic — but that window was explicitly time-limited, and it is widely reported to have closed on 30 July 2026.
I have not been able to confirm that closure date in a direct reading of Shopify's own documentation, so treat it as likely rather than settled. The practical instruction is the same either way: open Apps → Script Editor in your admin right now and find out. If the source is still there, copy every script out of it today, into a repository, before the question becomes academic. If it is not, the rest of this article is how you proceed.
One thing is settled: there is no Admin API that returns Script source or metadata. The only script-shaped object in the GraphQL Admin API is ScriptTag, which is a different feature entirely — remote JavaScript loaded into storefront pages, exposing a URL rather than any checkout logic. If you were hoping to script your way out of this, that is the dead end you will find.
Start with the report Shopify kept for you
Before writing any code, check whether Shopify already has your inventory.
Go to Apps → Script Editor and open the Replace Shopify Scripts banner. Shopify generates a Scripts customizations report per store: the customizations that were active immediately before deprecation, grouped into payment gateways, shipping and product discounts, with suggested Functions documentation or replacement apps against each. It exports to CSV.
Export it now. Shopify has not published an end date for the report, which is not the same as a promise that it will persist.
Then be clear about what you have. The report is a list of names. It tells you a shipping script existed and what it was called. It does not tell you it zeroed the rate above a threshold for accounts tagged trade, and only on domestic orders, and only when no other discount was already on the cart. That is the specification you actually need, and for that you need the orders.
Auditing your Scripts from order history
Here is the part worth being precise about, because it is the difference between a useful method and a false promise.
You are not recovering the Ruby. The source is not stored on your orders and cannot be derived from them. What you are recovering is evidence of behaviour: a record of what a script did on the occasions it ran. From enough of those records you can infer the rule that produced them, and that inference is usually good enough to rebuild against. It is still an inference, and the section below is explicit about where it breaks down.
The mechanism is that discount applications are recorded against the order, not against the script. Where a script applied a discount, Shopify stored a ScriptDiscountApplication in that order's discountApplications — carrying the title the original developer typed into the Ruby, whether the value was a percentage or a fixed amount, whether it targeted line items or the shipping line, and how it was allocated across entitled items. Because that record lives on the order, it was not affected by Scripts ceasing to execute. Confirm that on your own store before relying on it: run the query below against a date range where you know a script was live.
One constraint shapes the whole approach. The orders query has no filter for discount application type, so you cannot ask Shopify for orders with a script discount. You sweep a date range and filter on __typename yourself. Start small:
query ScriptDiscountSample($cursor: String) {
orders(
first: 50
after: $cursor
query: "created_at:>=2026-04-01 AND created_at:<=2026-06-30"
) {
pageInfo { hasNextPage endCursor }
edges {
node {
id
name
createdAt
discountApplications(first: 10) {
edges {
node {
__typename
allocationMethod
targetSelection
targetType
value {
... on MoneyV2 { amount currencyCode }
... on PricingPercentageValue { percentage }
}
... on ScriptDiscountApplication { title }
}
}
}
}
}
}
}The full sweep, for stores with real order volume
Paginating two years of orders fifty at a time is slow enough to be irritating on a store doing meaningful volume. A bulk operation is the right tool — but it cannot take the shape of the query above, and the reason is worth understanding rather than working around blindly.
Bulk operations require nested connections whose nodes implement the Node interface, and they cap you at five connections nested no more than two levels deep. DiscountApplication has no id field and does not implement Node, so nesting discountApplications inside a bulk query is likely to be rejected. LineItem does implement Node, and its discountAllocations field is a plain list rather than a connection — so it does not count against the connection budget and can carry the discount application through. That gives a legal shape:
The window is not arbitrary. Twelve months catches an annual cycle once. Twenty-four catches it twice, which is what distinguishes a Black Friday script someone published in 2023 and forgot from a rule that runs every year and will be missed this November.
mutation ScriptForensics {
bulkOperationRunQuery(
query: """
{
orders(query: "created_at:>=2024-09-01") {
edges {
node {
id
name
createdAt
paymentGatewayNames
lineItems {
edges {
node {
id
discountAllocations {
allocatedAmountSet { shopMoney { amount currencyCode } }
discountApplication {
__typename
allocationMethod
targetSelection
targetType
value {
... on MoneyV2 { amount currencyCode }
... on PricingPercentageValue { percentage }
}
... on ScriptDiscountApplication { title }
}
}
}
}
}
}
}
}
}
"""
) {
bulkOperation { id status }
userErrors { field message }
}
}query {
currentBulkOperation {
id
status
objectCount
url
partialDataUrl
}
}jq -r 'select(.discountAllocations != null)
| .discountAllocations[]
| .discountApplication
| select(.__typename == "ScriptDiscountApplication")
| [ .title,
.targetType,
.allocationMethod,
(.value.percentage // .value.amount) ]
| @tsv' bulk-orders.jsonl \
| sort | uniq -c | sort -rnReading the output
Read it as a frequency table. A title appearing on nine thousand orders at a flat 10% is a standing trade discount and is your first rebuild. A title appearing on four hundred orders across two weeks in November, twice, is a seasonal promotion that will be missed this year if nobody rebuilds it. A title appearing eleven times in two years is probably dead and worth a conversation before anyone spends money on it.
The distribution of values under a single title is the other half. One value means a flat rule. Several clustered values usually means tiering, and the order subtotals at which the value changes suggest the thresholds — not because the threshold was recorded anywhere, but because the boundary shows up in the data. Treat those numbers as hypotheses to confirm, not as retrieved settings.
Evidence, not source: what this cannot recover
The method has real edges, and being straight about them is the difference between a rebuild that holds and one that surprises you in November.
Logic that never fired left no evidence. A rule that excluded sale items only proves it existed on an order where someone tried to combine them. If nobody ever did, there is nothing to find.
The reasoning is gone, and no amount of order data returns it. You can establish that free shipping applied above a particular threshold. You cannot establish why that number — margin analysis, a competitor's figure, or something someone picked in 2019 and nobody revisited.
Payment scripts leave almost nothing. A line item script records what it did. A payment script that hid a gateway records nothing at all, because the absence of a gateway on an order is not a stored fact. paymentGatewayNames across the history gestures at it: if cash on delivery never appears above a certain order value, something was probably suppressing it. That is a hypothesis, not evidence, and it needs checking against someone's memory before anyone rebuilds to it.
Interaction between rules is only partly visible. The index field gives you precedence where two applications landed on the same order, which is genuinely useful. It tells you nothing about combinations that were mutually exclusive by design and therefore never co-occurred.
So treat the output as a strong first draft of the specification, then take it to whoever owns the commercial relationships. Reconstruction plus one conversation with the person who runs the wholesale accounts gets you most of the way. Either alone does not.
Rebuilding in Functions
Some of it ports cleanly. Percentage and fixed discounts on entitled products, free shipping thresholds, and hiding a payment method above a value are all well covered by Shopify Functions, and for the simplest cases an off-the-shelf app is a legitimate answer.
The model is genuinely different, though, and that difference is where migrations overrun. Scripts were Ruby with the cart in front of you — you could read whatever you liked and branch however you wanted. Functions are input-query-driven: you declare in advance what data your function receives, it runs against a fixed schema inside an execution budget, and anything outside that query is unavailable at runtime. Logic that relied on reading arbitrary customer or product state at checkout has to be restructured, usually by moving that state into metafields the input query can reach.
That restructuring, not the discount arithmetic, is the actual work. We hit the same boundary rebuilding checkout logic on Forged 4x4, where the calculation needed inputs that were not on the cart and had to be staged before the extension could see them — the Forged 4x4 checkout rebuild has the detail.
When this needs an engineer
Not always, and it is worth being straight about that.
If the report and the order sweep between them show one flat percentage for tagged customers, or a single free shipping threshold, that is a native automatic discount or a well-reviewed app. It does not need a developer, and it does not need us.
It needs an engineer when the reconstruction shows tiering with thresholds you had to infer, when several rules interacted and the precedence mattered, when B2B price lists or company locations are in play, or when a payment script's behaviour can only be established by inference. Those are the cases where an app gets you eighty per cent of the way and the missing twenty per cent is the part your customers notice. Rebuilding those properly is Shopify Plus engineering work, and it starts with the audit above, not with a rewrite.
Send us the store and the symptom
If you want this done rather than described: we run the reconstruction as a fixed piece of work. We sweep your order history, produce the behavioural specification — every rule that fired, how often, at what value, against what — and mark plainly which parts are evidence and which are inference. You get that document whether or not you then have us rebuild anything, because it is worth having on its own.
If you do go on to rebuild, the specification is the input to the Functions work, and the parts we flagged as inference are the ones we confirm with you before a line of code is written.
Send us the store and the symptom — the discount that stopped, the invoice that looked wrong, the margin number nobody can explain. If it turns out to be a config change, we will tell you that and you can go and make it.