Back to blog
ShopifyDeveloper ToolingOnline Stores

Shopify Scripts End June 30, 2026: The Developer Migration Guide

Shopify Scripts stop executing June 30, 2026. The developer migration guide: which Script types map to which Functions, with code examples.

Published April 14, 2026//9 min read

As of today, April 14, you have 77 days before Shopify Scripts stop executing entirely.

As of tomorrow, April 15, you can no longer edit or publish Scripts at all.

This is not a soft deadline. Shopify has confirmed no extensions. If your store has Line Item Scripts, Shipping Scripts, or Payment Scripts running in production, the checkout logic they power will silently stop working on June 30 unless you have migrated to Shopify Functions.

Here is the migration guide.

The timeline, plainly

Two dates matter.

April 15, 2026 — Editing and publishing new Shopify Scripts is blocked. Any Scripts currently running stay live until June 30, but you cannot modify them or create new ones. If something breaks in a Script today, you cannot patch it.

June 30, 2026 — All Scripts stop executing. Your checkout reverts to unmodified Shopify behavior. Any discounts, shipping customizations, or payment rules your Scripts implement simply disappear.

What's actually running in your store

Before you can migrate, you need to know what you have. Shopify Scripts come in three types.

Line Item Scripts run during cart and checkout to modify line items — applying discounts, changing prices, adding or removing items, or blocking checkout under certain conditions. These are the most common Scripts in production.

Shipping Scripts run at the shipping selection step to hide, rename, reorder, or apply discounts to delivery methods.

Payment Scripts run at the payment step to hide, rename, or reorder payment methods based on cart contents, customer data, or other conditions.

To see what is running, go to your Shopify admin → Settings → Apps and sales channels → Script Editor. Shopify also provides a Scripts customizations report that lists all active Scripts, what they target, and when they were last modified.

The migration map

Each Script type maps to one or more Function APIs depending on what the Script actually does. Here is the complete mapping.

Script type What it does Function replacement What Functions add
Line Item Script — discount Apply percentage, fixed, or conditional discounts Discount Function API (cart.lines.discounts.generate.run) Tiered pricing, buy-X-get-Y, explicit stacking strategy
Line Item Script — cart transform Merge, bundle, or add line items Cart Transform Function API True parent/child bundling with distinct line items
Line Item Script — validation Block checkout under conditions Cart and Checkout Validation Function API Block at cart step or checkout step, with custom messaging
Shipping Script — customization Hide, rename, or reorder delivery options Delivery Customization Function API Filter by customer tags, cart attributes, or delivery address
Shipping Script — discount Apply discounts to shipping rates Discount Function API (cart.delivery-options.discounts.generate.run) Same discount model as line item discounts
Payment Script Hide, rename, or reorder payment methods Payment Customization Function API Hide or rename based on cart total, customer tags, or B2B rules

The most common Script in production is the Line Item Script for discounts. That migration path has the most documentation and community tooling behind it.

Why Functions are architecturally different

This is the part most migration guides skip. Functions are not Scripts rewritten in a different language. The execution model is different, and that matters for how you write them.

Scripts ran Ruby code in a sandboxed environment. They received a mutable cart object and modified it directly — adjusting line item prices, adding items, blocking checkout. All logic lived in one file and ran against one shared context.

Functions receive an immutable JSON input describing the cart state and return a structured JSON output declaring what operations to apply. Nothing is mutated in place. Shopify reads the operations array your Function returns and applies the changes.

// Functions return operations — they never mutate state directly
export function run(input) {
  // input.cart is read-only
  // You declare what should change, not how to change it
 
  return {
    discounts: [
      {
        targets: [{ cartLine: { id: "gid://shopify/CartLine/1" } }],
        value: { percentage: { value: "10.0" } },
      },
    ],
    discountApplicationStrategy: DiscountApplicationStrategy.First,
  };
}

The practical implication: if your Script expressed complex stateful logic that modified the cart across multiple passes, you need to rethink that logic as a declarative output description rather than an imperative mutation sequence.

Functions also compile to WebAssembly and run directly on Shopify's infrastructure. Execution is under 5ms with no cold starts. The Ruby sandbox Scripts ran in was slower and more constrained. Functions are genuinely more capable once you are past the learning curve.

One important constraint: JavaScript Functions do not support async/await. The Javy runtime (Shopify's JavaScript-to-WebAssembly compiler) has no event loop — promises will compile but throw at runtime. If your Scripts depend on external API calls, you will need to use Functions with network access, which is a separate setup path.

Code: a discount Script vs a Discount Function

Here is the same logic — apply 10% off to any product tagged sale — expressed in the old Script model and the new Function model.

Before: Shopify Script (Ruby)

# Line Item Script — 10% off products tagged "sale"
Input.cart.line_items.each do |line_item|
  product = line_item.variant.product
  next unless product.tags.include?('sale')
 
  line_item.change_line_price(
    line_item.line_price * 0.9,
    message: "10% off sale items"
  )
end
 
Output.cart = Input.cart

After: Shopify Function (JavaScript)

// src/run.js — Discount Function: 10% off products tagged "sale"
import { DiscountApplicationStrategy } from "../generated/api";
 
const EMPTY_RESULT = {
  discounts: [],
  discountApplicationStrategy: DiscountApplicationStrategy.First,
};
 
export function run(input) {
  const targets = input.cart.lines
    .filter(
      (line) =>
        line.merchandise.__typename === "ProductVariant" &&
        line.merchandise.product.tags.includes("sale")
    )
    .map((line) => ({ cartLine: { id: line.id } }));
 
  if (!targets.length) return EMPTY_RESULT;
 
  return {
    discounts: [
      {
        targets,
        value: { percentage: { value: "10.0" } },
        message: "10% off sale items",
      },
    ],
    discountApplicationStrategy: DiscountApplicationStrategy.First,
  };
}

The structural shift is visible: the Script mutated line_item objects directly. The Function filters the input into targets and declares the discount as a return value. The outcome is the same. The execution model is not.

The Function is scaffolded with shopify app generate extension --template discount and deployed via shopify app deploy. It runs as part of an app installation — either a custom app for Shopify Plus stores, or a public app from the Partner Dashboard.

How to migrate without disrupting customers

Shopify's official migration guide recommends a customer-tag testing strategy that lets you run the new Function alongside the existing Script before cutting over. It is the safest migration pattern available.

Step 1. Deploy your new Function, but gate its logic to customers tagged TESTER.

export function run(input) {
  const customerTags = input.cart.buyerIdentity?.customer?.tags ?? [];
  const isTester = customerTags.includes("TESTER");
 
  if (!isTester) return EMPTY_RESULT;
 
  // Your new Function logic runs only for TESTER-tagged customers
  // ... rest of implementation
}

Step 2. In Shopify admin, add the TESTER tag to one or two internal customer accounts.

Step 3. In Script Editor, use the Script preview URL feature to bypass the old Script for your test sessions, so the old Script and new Function do not run simultaneously for the same customer.

Step 4. Place test orders as a TESTER customer. Verify the discount behavior, shipping rules, or payment filtering matches the old Script output exactly.

Step 5. Once verified, remove the TESTER gate from your Function so it runs for all customers, then deactivate the old Script.

Untagged customers continue hitting the existing Script throughout testing. You only cut over after you have confirmed the Function behavior matches.

Pre-migration audit checklist

Run through this before writing a line of new Function code.

  • Open Script Editor and list every active Script by type (Line Item, Shipping, Payment)
  • For each Script, document what it does in plain language — not just what it is named
  • Note which Scripts were last modified and who owns them
  • Identify whether each Script applies discounts, transforms items, validates cart, or customizes shipping/payment
  • Map each Script to the correct Function API using the migration map above
  • Flag any Scripts that make external API calls — those need the network access path
  • Confirm your store is on Shopify Plus (required for custom app extensions)
  • Decide for each Script: build a custom Function or use a third-party app

Build it yourself or use an app

Not every team needs to write custom WebAssembly-compiled Functions. For common discount, shipping, and payment rules, there are mature no-code apps built on Shopify Functions that replicate Script behavior through the admin.

Build a custom Function Use a no-code app
Requires Shopify CLI, JavaScript or Rust, app deployment Shopify admin access, app subscription
Good for Logic unique to your store, complex rule combinations Standard discounts, shipping rules, payment filters
Time to migrate Hours to days depending on complexity Minutes to hours
Ongoing maintenance You own the code App vendor owns the logic
Shopify Plus requirement Yes — custom app extensions only No — apps available on all plans
Flexibility ceiling None — it is code Bound by the app's UI and rule builder

If your Script is expressing standard logic — percentage discounts, order minimums, hide a shipping method by tag — an app is probably the faster and lower-maintenance path. If your Script encodes business rules that are unique to your store, write a custom Function.

For AI-assisted development of custom Functions, the tooling covered in Claude Code vs Cursor vs Codex for Shopify development applies directly. Functions are JavaScript or Rust, so the standard coding agent workflow works well. The Shopify Dev MCP server also validates generated Function code before deployment — the same server that helps with theme work covers Functions too.

Tradeoffs and parity gaps

Functions are better than Scripts in most areas, but there are real differences to plan around.

One Function per capability. A Script could express discount logic, cart validation, and item transformation all in one Ruby file. Functions are isolated by API — you need a Discount Function, a separate Cart and Checkout Validation Function, and a separate Cart Transform Function if your logic spans all three. This is cleaner architecturally but requires more upfront planning if your Script was doing multiple things at once.

Stacking behavior is now explicit. Scripts stacked discounts implicitly based on execution order. Functions require you to declare a discountApplicationStrategy: First (highest discount wins), Maximum (highest single discount applies), or All (all apply and combine). If your Script relied on implicit stacking behavior, you need to understand and declare that behavior explicitly in the Function.

No async/await in JavaScript Functions. The Javy runtime (Shopify's JS-to-WebAssembly compiler) has no event loop — promises compile but throw at runtime. Any external API calls your Script depended on need to be rearchitected using the network access feature.

Buy-X-Get-Y logic needs rewriting, not translating. The old Script pattern for buy-one-get-one maps to the Discount Function API's tiered discount model, but the input fields, targeting logic, and return shape are different enough that a line-for-line port will not work. Plan to rewrite it.

None of these are blockers. They are planning requirements. The migration is achievable before June 30 for most stores — the key is starting the audit now rather than in June.

Final recommendation

If you have Shopify Scripts in production, do this in order:

  1. Audit now. Use the Script Editor and the Scripts customizations report to list everything running.
  2. Map each Script to its Function replacement using the table above.
  3. Decide build vs. app for each Script based on logic complexity and team capacity.
  4. Deploy and test using the customer-tag strategy before cutting over.
  5. Deactivate Scripts only after verifying the Function produces identical behavior.

The Scripts freeze is tomorrow. The hard stop is June 30. Start with the audit today — everything else follows from knowing exactly what you have running.

// related