Blog

Replace Shopify Scripts Without Rewriting Your Discount Logic

Shopify Scripts retires June 30, 2026. There are three real ways to replace it. This post explains when each fits, what the migration actually costs, and how a config-driven engine sidesteps a full rewrite.

·11 min read

Three Shopify Scripts replacement paths laid out side by side: hand-port to Functions, generic discount apps, and a config-driven engine.

TL;DR. You have three options for replacing Shopify Scripts: hand-port every script to a Shopify Function, switch to a generic discount app, or run a config-driven engine on top of Functions. Hand-porting is correct for one or two simple scripts and a dev team with capacity. Generic apps cover standard discounts but break on bundle math and trade pricing. A config-driven engine is the shortcut when the rules are complex and the deadline is real. Background on the sunset itself is in Shopify Scripts Sunset.

The hidden cost of "just rewrite it in Functions"

Shopify Functions is the right runtime. The problem is everything around it. To ship a single Function in production you need:

Concretely: a tiered discount that takes twelve lines of config in an engine looks like this in raw Functions JavaScript:

// shopify-function: discount.run.js (excerpt)
export function run(input) {
  const TIERS = [
    { qty: 6, percent: 20 },
    { qty: 3, percent: 10 },
  ];
  const sofas = input.cart.lines.filter((l) => l.merchandise.product.tags.includes('sofa'));
  const totalQty = sofas.reduce((n, l) => n + l.quantity, 0);
  const tier = TIERS.find((t) => totalQty >= t.qty);
  if (!tier) return { discounts: [] };
  return {
    discounts: [
      {
        targets: sofas.map((l) => ({ productVariant: { id: l.merchandise.id, quantity: l.quantity } })),
        value: { percentage: { value: tier.percent } },
        message: `${tier.percent}% off ${totalQty} sofas`,
      },
    ],
    discountApplicationStrategy: 'FIRST',
  };
}

Plus the shopify.app.toml registration, plus a tests/discount.run.test.js mock harness, plus a deploy. The same logic in a config-driven engine is a JSON object the merchant edits in Shopify Admin. Both produce the same Function output; only one needs an engineer.

Three side-by-side cards comparing replacement paths: hand-port to Functions, generic discount app, and config-driven engine — each with what it is and best-for criteria.
Three replacement paths — what each is and who it fits.

Option A — Hand-port to Functions

When it fits. One or two simple scripts (a single percent-off, a shipping-method hide). In-house developer team with Shopify Functions experience. No deadline pressure — you can spend a quarter on tooling and tests.

When it does not. More than three scripts, or any rule with bundle math, tier overlap, or trade-customer gating. The toolchain cost is per-script, not amortised.

Effort estimate. A simple percent-off script is one to two developer days end-to-end (CLI setup, Function code, deploy, smoke test). A bundle script with shared quantity pools is one to three weeks. Multiply by the number of scripts and add 30% for review and rollback runs.

Option B — Generic discount apps

When it fits. Standard percent-off, fixed-amount, BOGO, code-based discounts. A handful of rules, all expressible as "if cart contains X, apply Y."

Where they fall over.

If your scripts are doing any of those, a generic app is not your migration path.

Option C — Config-driven engine (Discount Engine)

Same Shopify Functions runtime underneath. The difference is that the discount logic is a JSON config edited in Shopify Admin instead of a Rust or JavaScript file edited by a developer. The engine ships pre-built handlers for the four most common Scripts use cases — tier, bundle, split bundle, trade — and they cover the cases generic apps miss.

A real Scripts-era rule rewritten as Discount Engine config:

"Buy 3 sofas from the modular range, the cheapest is 50% off, capped at 2 free per cart."

In Ruby Scripts that was around 80 lines of Cart.line_items.each plus tag matching plus a price-min reducer. In Discount Engine it is a BundleDiscount config block:

{
  "type": "BundleDiscount",
  "name": "Modular sofa — 3 for 50% off cheapest",
  "groups": [
    { "tag": "sofa-modular", "minQuantity": 3 }
  ],
  "discount": { "kind": "cheapest", "percentage": 50 },
  "maxApplications": 2,
  "channels": ["online", "pos"]
}

Twelve lines. No deploy. The merchant turns it on from Shopify Admin and the next checkout uses it. Reference: /docs/bundle-discount.

The config-driven approach earns its keep when:

Decision matrix

Number of scriptsRule complexityInternal Functions experienceDeadline pressureRecommended option
1–2Simple (single % off, shipping hide)HighLowA. Hand-port
1–5Standard (percent-off, BOGO, codes)AnyAnyB. Generic discount app
3+Bundle, tier, trade, multi-targetAnyAnyC. Config-driven engine
AnyAnyNoneHighC. Config-driven engine
Decision matrix scoring three replacement options against five constraints: tight deadline, bundle math, no internal Functions developer, fast admin iteration, and trade/B2B segments.
Scoring each path against five real-world constraints.

The matrix is intentionally biased toward Option C in the bottom rows. If you do not have Functions experience in-house and the deadline is real, the engine is the only option that does not require hiring.

Migration playbook

Five steps. Order matters. Each step gates the next.

  1. Inventory. Open Shopify Admin → Apps → Script Editor and export every active script. Record what each one does in plain English and which products or customers it targets. The output is a spreadsheet, one row per script.
  2. Classify by handler type. Group each script by underlying mechanic: tier, bundle, split bundle, trade, BOGO, or shipping rule. This decides which replacement option fits and exposes scripts that overlap or contradict each other.
  3. Map to target. For each group, pick the replacement path: hand-port simple shipping rules; use a generic app for standard codes; use the engine for bundle, tier, and trade. A single store can mix all three — the choices are per-rule, not per-store.
  4. Parallel-run in a development store. Clone production into a dev store, install the replacement, recreate the rules. Run a synthetic checkout for every customer segment and product combination the original scripts cover. Diff the cart totals against production. Fix until they match.
  5. Cut over progressively. Migrate one discount at a time in production. Disable the corresponding Script Editor entry on cutover (do not delete — disabling is reversible). Watch order logs for one full sale cycle before moving to the next discount. Keep all Scripts disabled until the sunset date as a rollback path.

Sources & further reading

FAQ

Can I keep my existing Script Editor rules running while I migrate?

Yes. Until June 30, 2026 Scripts and Functions coexist on the same store. The recommended pattern is to build the replacement on a development store, validate one discount in production for a full sale cycle, then progressively cut over the rest with the option to roll back to Scripts at any point.

Does Discount Engine work at point of sale (POS)?

Yes. Discount Engine targets both online checkout and POS through the same Shopify Functions runtime, so a tier or bundle rule configured once applies to in-store sales as well as web orders. Trade discounts can be gated on customer attributes from either channel.

Will my Functions-based discounts combine with native automatic discounts?

Combinability is configured per discount in the Shopify admin. A Functions discount can be marked combinable with order, product, and shipping discounts, the same way a native automatic discount is — see Shopify's combining-discounts guide for the per-class rules. Discount Engine surfaces these flags in its config UI so you do not need to manage them in raw code.

How does this work with checkout extensibility?

Shopify Functions is the official discount engine for the new Checkout Extensibility platform — it is the same runtime that powers all native checkout customisation going forward. Anything you migrate from Scripts to a Function will continue to work as Shopify ships further checkout changes.

What is the realistic effort to hand-port a script to Functions?

A simple percent-off line-item script is one to two developer days, including the Shopify CLI setup, deployment pipeline, and a basic test. A complex bundle or tiered script with shared quantity pools is one to three weeks of dev time, plus QA. Most Plus stores have between three and a dozen active scripts.

Can I see the engine before requesting access?

Yes. The full configuration reference for every supported discount type lives at /docs, including bundle, tier, special bundle, and trade discounts with worked examples and the exact admin UI fields. Read the docs first, then email team@devkind.com.au for a custom install link to your store.

Read next

access

Custom distribution. Assisted install.

Shopify Discount Engine is distributed directly — not via the public App Store. Email for access and we'll walk through the engine and install it on your store.

terminal — access
$ email hassan.jsx@gmail.com \\
    --subject "discount-engine access" \\
    --store your-store.myshopify.com
 custom install link provided for your shop
 onboarding covers configuration and go-live