Replace Shopify Scripts with Functions: Tiered Discount Guide

Shopify Scripts stopped running on June 30, 2026. How to rebuild a tiered discount Script as a Discount Function in a custom app and activate it.

Cover image for "Replace Shopify Scripts with Functions: Tiered Discount Guide": the title on a dark green background next to an illustration of the topic

If your tiered discount Script stopped working, it's because Shopify switched off all Scripts on June 30, 2026, and nothing will turn them back on. To replace Shopify Scripts with Functions, you rebuild the same logic as a Discount Function inside an app, deploy it with Shopify CLI, and create an automatic discount that runs it.

This guide covers the full path for the most common case: a "spend $100, get 10% off; spend $200, get 20% off" Script, plus the quantity-tier variant.

The short answer

  • Scripts are gone. Shopify's developer changelog set two dates: editing and publishing Scripts ended on April 15, 2026, and all Scripts stopped executing on June 30, 2026.
  • Discounts now come from the Discount Function API. It's one API with two run targets: cart.lines.discounts.generate.run for product and order discounts, and cart.delivery-options.discounts.generate.run for shipping discounts.
  • You need an app. A function ships as an extension inside an app. A public app from the App Store works on any plan. A custom app with functions works only on Shopify Plus, according to Shopify's Functions overview.
  • A function does nothing until it's attached to a discount. You create an automatic discount that points at your function, and only then does it run at checkout.

What happened to Shopify Scripts

Scripts were small Ruby programs edited in the Script Editor app. They came in three types: line item scripts, shipping rate scripts, and payment scripts (the names Shopify still uses on its Scripts limitations page).

Shopify's Script Editor help page now says it plainly: Shopify Scripts has been deprecated as of June 30, 2026, and any Scripts still published on a store were deactivated. If you didn't migrate before that date, your tiered pricing simply stopped applying. Customers kept checking out, just without the discount.

Before you rebuild anything, open the Scripts customizations report in your admin. The migration help page describes it as a personalized page that lists the Scripts that were active on your store, and you can export it as CSV. That list is your migration checklist.

Map each Script type to a Function API

Shopify no longer publishes a one-to-one migration table, so here is how I map the jobs Scripts used to do onto the current Function APIs. One Script often did several jobs, so it may split into more than one function.

Old Script does this Use this Function API
Percentage or fixed discounts on products or the order (line item script) Discount, target cart.lines.discounts.generate.run
Bundles or merged line items (line item script) Cart Transform
Blocking checkout under certain conditions Cart and Checkout Validation
Discounting shipping rates (shipping script) Discount, target cart.delivery-options.discounts.generate.run
Hiding, renaming or reordering shipping methods (shipping script) Delivery Customization
Hiding, renaming or reordering payment methods (payment script) Payment Customization

Which discount API is current

In 2025, Shopify replaced its three separate discount APIs (Product Discount, Order Discount and Shipping Discount) with one Discount Function API. The deprecation notice, posted May 21, 2025, says the old APIs are deprecated as of API version 2025-04 and that developers had to move before 2026-04. Many tutorials still show the old run.graphql and discountApplicationStrategy shapes. Ignore them. Everything below uses the current Discount Function API reference.

How to replace Shopify Scripts with Functions: a tiered discount

The example below rebuilds a subtotal-tier Script. The tiers live in a JSON metafield on the discount, so you can change them later without redeploying code. Shopify's own reference includes a similar "tiered discounts based on order subtotal" example, and this version follows the same schema.

1. Create the app and the extension

You need a Shopify Partner account or Dev Dashboard access, a dev store, Node.js, and the latest Shopify CLI. Shopify's discount tutorial lists Node.js 22 or higher. If the app is new, create it with shopify app init. New custom apps can no longer be created in the Shopify admin, so they're built with the CLI or the Dev Dashboard. I explain that change in legacy custom apps are deprecated.

Then generate the discount extension and pick JavaScript when prompted:

shopify app generate extension --template discount --name tiered-discount

The template creates a shopify.extension.toml with both targets. Open shopify.app.toml and make sure the app requests the write_discounts scope:

[access_scopes]
scopes = "write_discounts"

2. Write the input query

The function only receives the fields you ask for. Edit src/cart_lines_discounts_generate_run.graphql:

query Input {
  cart {
    cost {
      subtotalAmount {
        amount
      }
    }
  }
  discount {
    discountClasses
    metafield(namespace: "$app:tiered-discount", key: "function-configuration") {
      jsonValue
    }
  }
}

discountClasses tells the function which kinds of savings the discount is allowed to give. The metafield uses the app-reserved $app prefix, which Shopify recommends for function configuration so other apps can't write to it. After changing the query, run shopify app function typegen to refresh the generated types.

3. Write the run function

Replace the generated cart lines function with this:

// @ts-check
import {
  DiscountClass,
  OrderDiscountSelectionStrategy,
} from "../generated/api";

/**
 * @param {import("../generated/api").Input} input
 * @returns {import("../generated/api").CartLinesDiscountsGenerateRunResult}
 */
export function cartLinesDiscountsGenerateRun(input) {
  // Only give order savings if the discount allows the ORDER class.
  if (!input.discount.discountClasses.includes(DiscountClass.Order)) {
    return { operations: [] };
  }

  const config = input.discount.metafield?.jsonValue;
  const tiers = Array.isArray(config?.tiers) ? config.tiers : [];
  const subtotal = parseFloat(input.cart.cost.subtotalAmount.amount);

  // Pick the highest threshold the cart has reached.
  const tier = tiers
    .filter((t) => subtotal >= t.threshold)
    .sort((a, b) => b.threshold - a.threshold)[0];

  if (!tier) {
    return { operations: [] };
  }

  return {
    operations: [
      {
        orderDiscountsAdd: {
          candidates: [
            {
              message: `${tier.percentage}% off orders over ${tier.threshold}`,
              targets: [{ orderSubtotal: { excludedCartLineIds: [] } }],
              value: { percentage: { value: tier.percentage.toFixed(1) } },
              conditions: [
                {
                  orderMinimumSubtotal: {
                    minimumAmount: tier.threshold.toFixed(1),
                    excludedCartLineIds: [],
                  },
                },
              ],
            },
          ],
          selectionStrategy: OrderDiscountSelectionStrategy.First,
        },
      },
    ],
  };
}

A few details matter here. The output is a list of operations, and each operation type (orderDiscountsAdd, productDiscountsAdd) can appear only once per result. The orderSubtotal target applies the percentage to the whole subtotal, and excludedCartLineIds lets you leave specific lines out, such as gift cards. The conditions block is optional, but the reference documents minimumAmount in the shop's currency, so Shopify checks the threshold too.

Quantity tiers instead of spend tiers

If your Script gave "buy 5, save 10%; buy 10, save 20%" on each line, request cart.lines { id quantity } in the input query and return a productDiscountsAdd operation instead. Each candidate targets one line with targets: [{ cartLine: { id: line.id } }] and a percentage value. Set selectionStrategy to ProductDiscountSelectionStrategy.All so every qualifying line gets its tier, and check for DiscountClass.Product instead of Order. The discount must also be created with the PRODUCT class.

Activating it as an automatic discount

Deploying code doesn't create a discount. You attach the function to an automatic discount with the discountAutomaticAppCreate mutation, which needs the write_discounts scope.

Start shopify app dev, press g in that terminal to open GraphiQL, and run:

mutation {
  discountAutomaticAppCreate(
    automaticAppDiscount: {
      title: "Spend more, save more"
      functionHandle: "tiered-discount"
      discountClasses: [ORDER]
      startsAt: "2026-11-01T00:00:00Z"
      combinesWith: {
        orderDiscounts: false
        productDiscounts: true
        shippingDiscounts: true
      }
      metafields: [
        {
          namespace: "$app:tiered-discount"
          key: "function-configuration"
          type: "json"
          value: "{\"tiers\":[{\"threshold\":100,\"percentage\":10},{\"threshold\":200,\"percentage\":20}]}"
        }
      ]
    }
  ) {
    automaticAppDiscount {
      discountId
    }
    userErrors {
      field
      message
    }
  }
}

Use functionHandle, which is the handle value in shopify.extension.toml. The tutorial says that from API version 2025-10 you no longer need to look up function IDs, and functionId is now marked deprecated on the input object. Once it's created, the discount appears under Discounts in the admin, where staff can pause or end it.

When it works on the dev store, run shopify app deploy to release an app version, then install that version on the live store and run the same mutation there.

Testing before customers see it

  • Deactivate other discounts on the dev store first, as the tutorial suggests, so you know which discount is doing the work.
  • Test each boundary. Try a cart at $99.99, $100, $199.99 and $200. When I port a Script, I check whether the old Ruby code used > or >=, because an exact $100 cart is where a port usually goes wrong.
  • Read the execution logs. While shopify app dev runs, it shows each function execution. Run shopify app function replay to rerun a real execution locally, or shopify app function run --input input.json to feed it a hand-made cart.
  • Check every sales channel you use. The Discount Function API supports cart, checkout and POS, but the reference lists exceptions. The function isn't rerun when an order is edited, for example.

Common mistakes

  1. Using the old APIs. Code that returns discounts and discountApplicationStrategy was written for the deprecated Product or Order Discount APIs.
  2. Deploying but never creating the discount. No discount node means the function never runs.
  3. A custom app on a non-Plus store. Custom apps that contain functions only work on Shopify Plus. On other plans, use a public app from the App Store, or check whether Shopify's native automatic discounts cover your tiers.
  4. Ignoring discountClasses. If the discount was created with [ORDER] and your code returns product operations, they won't apply as you expect. Return only the classes the discount allows.
  5. Mismatched metafield namespace. The namespace and key in the mutation must match the input query exactly, or metafield comes back null and the function returns nothing.
  6. Too many functions. Shopify allows a maximum of 25 active discount functions per store, so don't build a separate function for each tier.

FAQ

When did Shopify Scripts stop working?

All Scripts stopped executing on June 30, 2026. Editing and publishing new Scripts ended earlier, on April 15, 2026.

Can I still edit or re-enable a Script?

No. Shopify's help center says Scripts still published at the cutoff were deactivated and no longer work. The replacement is a Shopify Function or an app.

Do I need Shopify Plus to replace a Script with a function?

Only if you use a custom app. Shopify says stores on any plan can use public App Store apps that contain functions, while custom apps with functions need Shopify Plus.

Can Shopify's built-in discounts handle tiered pricing?

Sometimes. Native automatic discounts support minimum purchase requirements, so check them first for simple tiers. A function makes sense when you need exclusions, customer conditions, or tiers that interact in ways the native options can't express.

Should I write the function in JavaScript or Rust?

Either works. Shopify recommends Rust for performance with large carts. JavaScript is fine for simple tier logic and easier for most teams to maintain.

Can one function discount products and shipping at the same time?

Yes. A single Discount Function can apply product, order and shipping savings. Shipping savings come from the delivery options target, so you implement that target as well.

When to get help

If your store is on Plus and your old Script did several things at once, such as tiers, customer tags and exclusions, the port is mostly about getting the edge cases right. I build custom Shopify apps with Discount Functions as a registered Shopify Partner, and I can rebuild your Script, test it against your real carts and hand over the code. See my Shopify app development service for details.

Have a project in mind? Let’s talk.

Tell me what you are building and where you need support. I’ll respond with a clear, practical next step.

Start your project