The Clock Has Run Out -- and Many Stores Are Not Ready
Shopify Scripts were officially deprecated on August 28, 2025. If you are reading this in 2026 and your store still relies on legacy Script Editor workarounds, you are operating on borrowed time. Shopify has been migrating merchants in waves, and the remaining grace periods are ending. Stores that have not transitioned to Shopify Functions are experiencing broken discount logic, checkout errors, and lost revenue -- right now.
This is not a theoretical problem. We have seen Shopify Plus stores lose five-figure sums in a single weekend because a Script that powered their tiered pricing simply stopped executing. No warning email caught in time. No fallback in place.
This article gives you everything you need: the full timeline, a side-by-side comparison, code examples for the most common use cases, a step-by-step migration plan, and an honest assessment of what it costs. Whether you migrate yourself or hire a team, you will walk away knowing exactly what needs to happen.
The Full Timeline: How We Got Here
Shopify did not drop this overnight. The deprecation was a multi-year process, but many merchants ignored the early signals.
| Date | Event |
|---|---|
| June 2022 | Shopify announces Shopify Functions as the successor to Scripts at Shopify Editions |
| January 2023 | First Function APIs become available (Discount, Shipping, Payment) |
| April 2023 | Shopify begins sending deprecation notices to Script Editor users |
| August 2023 | New Script creation is disabled -- existing Scripts continue to run |
| January 2024 | Shopify marks Scripts as "legacy" in the admin panel |
| June 2024 | Shopify announces the hard deprecation date: August 28, 2025 |
| February 2025 | Final migration reminders sent to all affected Plus merchants |
| August 28, 2025 | Scripts officially deprecated -- execution disabled for remaining stores |
| Late 2025 - 2026 | Rolling enforcement -- remaining Script-dependent stores lose functionality |
Key takeaway: If your store still has any dependency on Scripts -- even inactive ones that "might" be needed -- treat this as an emergency. The platform has moved on, and compatibility is no longer guaranteed.
Scripts vs. Shopify Functions: The Fundamental Differences
This is not a simple rename. Scripts and Shopify Functions are architecturally different systems. Understanding these differences is critical before you start migrating.
| Aspect | Shopify Scripts | Shopify Functions |
|---|---|---|
| Language | Ruby (subset) | Rust, JavaScript, or any language compiling to WebAssembly |
| Execution environment | Shopify's server-side Ruby sandbox | WebAssembly (Wasm) sandbox |
| Where they run | Server-side, during checkout | Server-side, at defined extension points |
| Performance | Variable, no hard limits | Strict: 11ms execution limit, 11MB memory |
| Configuration | Script Editor UI in admin | Shopify CLI + app deployment |
| Deployment | Paste code in browser editor | Build, deploy via CLI as part of a Shopify app |
| Access to data | Full cart object, customer data | Only data defined by the input query |
| Discount types | Line item, shipping, payment | Product discounts, order discounts, shipping, payment, delivery, cart transform |
| Extensibility | Limited to 3 script types | 15+ extension points and growing |
| Multi-store | Per-store configuration | Single app deployable to multiple stores |
| Version control | None (code lives in Shopify admin) | Full Git integration, CI/CD pipelines |
| Testing | Manual testing only | Unit tests, integration tests, local development |
The bottom line: Shopify Functions are more powerful, more performant, and more maintainable. But they require a fundamentally different workflow -- you are building and deploying a Shopify app, not pasting code into a text box.
What Scripts Were Typically Used For
Over the years, Shopify Plus merchants built Scripts for a surprisingly narrow set of use cases. Here are the most common ones, in order of frequency:
- Tiered discounts -- "Buy 3, get 10% off; buy 5, get 20% off"
- BOGO and bundle pricing -- "Buy one, get one free" or "Buy X + Y for a fixed price"
- Customer-tag-based pricing -- "VIP customers get 15% off everything"
- Automatic free gifts -- "Orders over $100 get a free sample"
- Shipping rate modifications -- "Free shipping for orders over $75" or "Hide express shipping for heavy items"
- Payment method control -- "Hide PayPal for B2B customers" or "Only show invoice payment for tagged accounts"
- Line item property-based pricing -- Custom pricing based on product customizations
- Geo-based discounts -- Different pricing for different countries or regions
The good news: Shopify Functions can handle every single one of these. The bad news: the implementation is completely different.
Shopify Functions Equivalents: Code Examples for Each Use Case
Use Case 1: Tiered Quantity Discounts
The old Script (Ruby):
DISCOUNTS_BY_QUANTITY = {
3 => 10,
5 => 20,
10 => 30,
}
Input.cart.line_items.each do |line_item|
quantity = line_item.quantity
applicable_discount = 0
DISCOUNTS_BY_QUANTITY.each do |qty, discount|
if quantity >= qty
applicable_discount = discount
end
end
if applicable_discount > 0
line_item.change_line_price(
line_item.line_price * (1 - applicable_discount / 100.0),
message: "#{applicable_discount}% volume discount"
)
end
end
Output.cart = Input.cart
The new approach (JavaScript):
First, define the input query to request the data you need:
# input.graphql
query Input {
cart {
lines {
id
quantity
merchandise {
... on ProductVariant {
id
product {
id
title
}
}
}
cost {
amountPerQuantity {
amount
currencyCode
}
}
}
}
discountNode {
metafield(namespace: "volume-discount", key: "config") {
value
}
}
}
Then, implement the logic:
// src/index.js
export function run(input) {
const config = JSON.parse(
input.discountNode.metafield?.value ?? '{"tiers":[]}'
);
const discountGroups = {};
input.cart.lines.forEach((line) => {
const quantity = line.quantity;
let percentage = 0;
for (const tier of config.tiers) {
if (quantity >= tier.quantity) {
percentage = tier.percentage;
}
}
if (percentage > 0) {
if (!discountGroups[percentage]) {
discountGroups[percentage] = [];
}
discountGroups[percentage].push({
productVariant: { id: line.merchandise.id },
});
}
});
const discounts = Object.entries(discountGroups).map(
([percentage, groupTargets]) => ({
message: `${percentage}% volume discount`,
targets: groupTargets,
value: {
percentage: {
value: percentage.toString(),
},
},
})
);
if (discounts.length === 0) {
return { discountApplicationStrategy: "FIRST", discounts: [] };
}
return {
discountApplicationStrategy: "FIRST",
discounts,
};
}
Key difference: The discount tiers are stored in a metafield, not hardcoded. This means merchants can update the tiers through the admin without redeploying.
Use Case 2: Customer-Tag-Based Discounts
// src/index.js
export function run(input) {
const customer = input.cart.buyerIdentity?.customer;
if (!customer) {
return { discountApplicationStrategy: "FIRST", discounts: [] };
}
const config = JSON.parse(
input.discountNode.metafield?.value ?? '{"rules":[]}'
);
// Example config stored in metafield:
// { "rules": [{ "tag": "VIP", "percentage": 15 },
// { "tag": "wholesale", "percentage": 25 }] }
const matchingRule = config.rules.find((rule) =>
customer.hasAnyTag && customer.hasAnyTag.includes(rule.tag)
);
if (!matchingRule) {
return { discountApplicationStrategy: "FIRST", discounts: [] };
}
const targets = input.cart.lines
.filter((line) => line.merchandise.__typename === "ProductVariant")
.map((line) => ({
productVariant: { id: line.merchandise.id },
}));
return {
discountApplicationStrategy: "FIRST",
discounts: [
{
message: `${matchingRule.tag} discount`,
targets,
value: {
percentage: {
value: matchingRule.percentage.toString(),
},
},
},
],
};
}
Use Case 3: Hiding Payment Methods
This uses the Payment Customization API:
# input.graphql
query Input {
cart {
buyerIdentity {
customer {
hasTags(tags: ["B2B", "wholesale"]) {
tag
hasTag
}
}
}
}
paymentMethods {
id
name
}
}
// src/index.js
export function run(input) {
const customer = input.cart.buyerIdentity?.customer;
const isB2B = customer?.hasTags?.some(
(t) => (t.tag === "B2B" || t.tag === "wholesale") && t.hasTag
);
const operations = [];
if (!isB2B) {
// Hide "Invoice" payment method for non-B2B customers
const invoiceMethod = input.paymentMethods.find(
(method) => method.name.includes("Invoice")
);
if (invoiceMethod) {
operations.push({
hide: {
paymentMethodId: invoiceMethod.id,
},
});
}
}
return { operations };
}
Use Case 4: Shipping Rate Modifications
Using the Delivery Customization API:
// src/index.js
export function run(input) {
const cartTotal = parseFloat(
input.cart.cost.totalAmount.amount
);
const operations = [];
if (cartTotal < 75.0) {
// Hide free shipping option for orders under $75
const freeShipping = input.cart.deliveryGroups
.flatMap((group) => group.deliveryOptions)
.find((option) => option.title.includes("Free"));
if (freeShipping) {
operations.push({
hide: {
deliveryOptionHandle: freeShipping.handle,
},
});
}
}
return { operations };
}
Step-by-Step Migration Guide
Step 1: Audit Your Current Scripts
Before writing a single line of code, document every Script running in your store.
Open Settings > Script Editor in your Shopify admin. For each active Script, record:
- Script name and type (line item, shipping, payment)
- What it does in plain language
- Business rules it implements (thresholds, conditions, exceptions)
- Dependencies -- does anything else rely on this Script's behavior?
- Revenue impact -- what happens if this Script stops working?
Create a spreadsheet. This becomes your migration checklist.
Step 2: Prioritize by Risk
Not all Scripts are equally critical. Rank them:
| Priority | Criteria | Example |
|---|---|---|
| Critical | Directly affects pricing or checkout completion | Tiered discounts, payment method filtering |
| High | Affects customer experience but has workarounds | Free gift with purchase, shipping modifications |
| Medium | Nice-to-have, business can operate without | Loyalty point calculations |
| Low | Rarely triggered, minimal business impact | Edge case discounts for specific promotions |
Migrate Critical and High priority Scripts first.
Step 3: Set Up Your Development Environment
Shopify Functions require the Shopify CLI and a Shopify app. If you do not have one yet:
# Install or update Shopify CLI
npm install -g @shopify/cli@latest
# Create a new app (or use an existing one)
shopify app init
# Generate an extension
shopify app generate extension --template discount_function_js
This scaffolds a complete project with:
input.graphql-- the query defining what data your extension receivessrc/index.js-- your logicshopify.extension.toml-- configurationpackage.json-- dependencies
Step 4: Implement and Test Locally
Write your logic based on the Script audit from Step 1. Use the Shopify CLI to test locally:
# Run locally with sample input
shopify app function run --input sample_input.json
# Run unit tests
npm test
Create a sample_input.json file that mirrors the data your extension will receive in production. This is your primary debugging tool.
Step 5: Deploy to a Development Store
# Deploy the app (including all extensions)
shopify app deploy
After deployment, activate through the Shopify admin:
- Go to Settings > Discounts (or Shipping/Payments depending on the type)
- Create a new discount and select your custom extension
- Configure it using the metafields or settings UI you built
- Test thoroughly in the development store checkout
Step 6: Test Edge Cases
This is where most migrations fail. Test these scenarios:
- Empty cart
- Single item at each quantity threshold
- Customer with no tags / multiple tags
- Guest checkout (no customer object)
- Mixed cart (discountable and non-discountable items)
- Discount stacking with existing automatic discounts
- Currency conversion (if selling internationally)
- Checkout in different languages
Step 7: Deploy to Production
Once testing is complete:
- Install the app on your production store
- Activate the new discount or customization
- Run both systems in parallel for 48-72 hours if possible (create the new discount but keep the Script active -- verify no double-discounting)
- Disable the legacy Script
- Monitor checkout completion rates and average order values for 7 days
Common Migration Pitfalls
Pitfall 1: Assuming 1:1 Feature Parity
Shopify Functions cannot do everything Scripts could. The most common gaps:
- No access to full customer order history within the execution itself. You need to pre-compute data and store it in metafields.
- No ability to modify line item properties directly. Use the Cart Transform API instead.
- No access to discount codes within product discount extensions. The discount either applies automatically or is configured through the admin.
Pitfall 2: Ignoring the 11ms Execution Limit
Shopify Functions must complete within 11 milliseconds. Scripts had no hard limit. If your Script does complex calculations -- iterating over hundreds of line items, checking multiple conditions per item -- you might hit the wall.
How to fix it: Simplify your logic. Pre-compute values and store them in metafields. Avoid nested loops. Use early returns to exit as fast as possible.
Pitfall 3: Hardcoding Business Rules
Scripts encouraged hardcoding because editing was easy -- just change the code in the browser editor. Shopify Functions require a deployment cycle. If you hardcode business rules, every change to a discount threshold means redeploying.
How to fix it: Store all configurable values in metafields. Build a simple settings UI in your app. This is more work upfront but saves you dozens of redeployments over the life of the extension.
Pitfall 4: Not Handling the "No Customer" Case
Guest checkout means the customer object is null. Many Scripts assumed a customer was always present. Your code will crash or behave unexpectedly if you do not handle this explicitly.
// Always check for customer existence
const customer = input.cart.buyerIdentity?.customer;
if (!customer) {
return { discountApplicationStrategy: "FIRST", discounts: [] };
}
Pitfall 5: Discount Stacking Conflicts
Scripts processed discounts in a single pass. Shopify Functions use a discount application strategy (FIRST, MAXIMUM, or ALL). If you have multiple extensions active, the interaction between them matters.
| Strategy | Behavior |
|---|---|
FIRST | Only the first applicable discount applies |
MAXIMUM | The discount with the highest value applies |
ALL | All applicable discounts stack |
Choose carefully. Setting everything to ALL can lead to unintended deep discounts.
What to Do When No Equivalent Exists
Some Script behaviors do not map cleanly to Shopify Functions. Here are the most common gaps and workarounds:
Complex Cart Manipulations
If your Script added, removed, or modified line items (e.g., automatic free gift insertion), use the Cart Transform API. This is a separate extension type specifically designed for modifying the cart structure.
// Cart Transform -- expand a line item to include a free gift
export function run(input) {
const operations = [];
const cartTotal = parseFloat(input.cart.cost.totalAmount.amount);
if (cartTotal >= 100) {
operations.push({
expand: {
cartLineId: input.cart.lines[0].id,
expandedCartItems: [
{
merchandiseId: "gid://shopify/ProductVariant/FREE_GIFT_ID",
quantity: 1,
price: {
adjustment: {
fixedPricePerUnit: {
amount: "0.00",
},
},
},
},
],
},
});
}
return { operations };
}
Dynamic Shipping Rate Calculations
If your Script calculated shipping rates dynamically (e.g., weight-based pricing with custom zones), use the Delivery Customization API to hide, reorder, or rename rates, or build a Carrier Service for fully custom rate calculation.
Multi-Currency Price Adjustments
Shopify Functions receive prices in the cart's presentment currency. If your Script applied different discount percentages based on currency or region, store those rules in metafields and reference them in your code.
Truly Unsupported Cases
If you find a use case that genuinely cannot be replicated:
- Check the Shopify changelog -- new APIs ship regularly
- Use Shopify Flow as a complementary tool for post-checkout logic
- Build a custom app with the Admin API for workflows that do not need to run at checkout speed
- Submit a feature request to Shopify -- they actively track migration blockers
Cost and Effort Estimation
Let us be honest about what this migration costs. The answer depends on your Script complexity.
Simple Migration (1-3 basic Scripts)
What qualifies: Straightforward percentage discounts, simple BOGO, basic payment hiding.
| Item | Estimate |
|---|---|
| Development time | 20-40 hours |
| Testing and QA | 10-15 hours |
| Deployment and monitoring | 5 hours |
| Total effort | 35-60 hours |
| Cost (agency/freelancer) | $3,500-$9,000 |
Medium Migration (4-8 Scripts with moderate logic)
What qualifies: Tiered pricing with multiple conditions, customer-tag-based discounts with exceptions, shipping rules with geo-logic.
| Item | Estimate |
|---|---|
| Audit and planning | 8-12 hours |
| Development time | 60-100 hours |
| Settings UI / metafield configuration | 15-25 hours |
| Testing and QA | 20-30 hours |
| Deployment and monitoring | 10 hours |
| Total effort | 113-177 hours |
| Cost (agency/freelancer) | $11,000-$27,000 |
Complex Migration (8+ Scripts, heavy business logic)
What qualifies: Interconnected Scripts, dynamic pricing engines, complex B2B rules, multi-currency logic, cart manipulation.
| Item | Estimate |
|---|---|
| Audit, architecture, and planning | 15-25 hours |
| Development time | 120-200+ hours |
| Settings UI and admin app | 30-50 hours |
| Testing and QA | 40-60 hours |
| Deployment, parallel run, and monitoring | 15-20 hours |
| Documentation and training | 10-15 hours |
| Total effort | 230-370+ hours |
| Cost (agency/freelancer) | $23,000-$55,000+ |
The Hidden Cost of Not Migrating
The numbers above might cause sticker shock. But consider the alternative:
- Broken checkout = lost revenue (we have seen $5,000-$50,000 in a single weekend)
- Manual discount workarounds = staff time + human error
- Customer complaints = brand damage, negative reviews, increased support load
- Platform incompatibility = you cannot use new Shopify features that assume the modern API
The math is simple: A store doing $500,000/year in revenue that loses 2% of sales to checkout issues from broken Scripts loses $10,000. The migration pays for itself within months -- often weeks.
The Migration Checklist
Use this as your working document:
Preparation
- Audit all active Scripts in Script Editor
- Document business rules for each Script
- Identify the API needed for each Script (Product Discount, Order Discount, Delivery Customization, Payment Customization, Cart Transform)
- Set up a development store for testing
- Install and configure Shopify CLI
Development
- Create a Shopify app (or use existing one)
- Generate extensions for each Script
- Implement input queries (
input.graphql) for each extension - Implement logic with configurable metafields
- Write unit tests for all edge cases
- Test locally with sample input data
Testing
- Deploy to development store
- Test each extension individually
- Test interactions between extensions (discount stacking)
- Test guest checkout scenarios
- Test multi-currency scenarios (if applicable)
- Test mobile checkout
- Load test with realistic cart sizes
- Verify 11ms execution limit compliance
Deployment
- Deploy app to production store
- Activate new extensions alongside existing Scripts (parallel run)
- Monitor for double-discounting or conflicts
- Deactivate legacy Scripts after 48-72 hours of successful parallel operation
- Monitor checkout KPIs for 7 days post-migration
- Remove Script Editor code (cleanup)
Post-Migration
- Document all extensions and their configurations
- Train merchant team on how to update metafield-based settings
- Set up monitoring and alerting for errors
- Schedule quarterly review of performance and business rules
Do Not Wait for It to Break
We have guided dozens of Shopify Plus merchants through this migration -- from single-Script stores to enterprises with 15+ interconnected Scripts powering complex B2B pricing engines. The pattern is always the same: merchants who migrated proactively had a smooth transition. Those who waited until Scripts broke spent more money, lost more revenue, and endured more stress.
TG-AI handles the entire migration process: audit, architecture, development, testing, deployment, and post-migration support. We build Shopify extensions in JavaScript and Rust, deploy them as managed Shopify apps, and ensure your discount logic works exactly as it did before -- often better, because the modern APIs unlock capabilities Scripts never had.
If you are still running Scripts, or if you migrated but something is not working correctly, reach out. We will start with a free audit of your current setup and give you an honest assessment of scope, timeline, and cost.
The deadline is not approaching. It has passed. The only question is how much it costs you to wait another week.