August 7, 2026

Shopify Functions for Discounts and Promotions

A technical walkthrough of Shopify Functions for Discounts on Plus. Product, Order, and Shipping Discount Function types, Rust/JS dev workflow and common patterns.
7 min read
Flux Insights Static HeroAdam, Fractional CEO, smiling man with short dark hair and beard wearing a black shirt in a bright office environment
Max Max Karlsson
Flux Insights Static Pattern  Hero

If you have not migrated your Shopify Scripts to Functions yet, the clock is running out. Scripts is on the deprecation path. Functions is the replacement. They are not feature-parity in every direction, but they cover almost every real discount and promotion use case on Plus.

This is the walkthrough for engineering teams sitting on a Scripts stack and trying to plan the migration. What Discount Functions are, the three function types, the dev workflow, what bites, and the patterns we end up reusing.

From Scripts to Functions: what actually changed

Shopify Scripts ran on Shopify's Ruby execution layer. Closed runtime. Limited tooling. No version control. They worked for years but were always a black box for the engineering teams that owned them.

Shopify Functions are a different model. You write logic in Rust or JavaScript, compile to WebAssembly, deploy via Shopify CLI, version with your app. The runtime is Shopify-hosted but the code is yours, in your repo, with proper testing and review workflow.

The migration matters for two reasons. One, Scripts is deprecating (announced 2023, full retirement on a multi-year runway, but Plus brands are being moved off). Two, Functions extend to more surfaces than Scripts ever did, including the Shopify checkout itself via Checkout Extensibility.

The three Discount Function types

Product Discount Function

Applies discounts to specific cart lines. Use case: "buy this product and get 20% off," or "this collection is 15% off for VIP members," or "discount applies only to specific variants."

Returns operations like:

  • productDiscounts: array of discounts to apply to specific line items

Granular. You target individual cart lines, individual variants, individual quantities within a line.

Order Discount Function

Applies discounts to the cart total. Use case: "10% off orders over $200," or "$15 off when cart contains items from two categories," or "free item over $300."

Returns operations like:

  • orderDiscounts: array of discounts to apply at the order level

Best for any logic that depends on cart composition rather than individual lines.

Shipping Discount Function

Modifies shipping rates and applies shipping discounts. Use case: "free shipping over $75," or "$5 off Express shipping for members," or "free Express shipping during a promo window."

Returns operations like:

  • discounts: array of shipping rate modifications

Tightly scoped, but the only clean way to implement conditional shipping logic server-side.

The development workflow

Scaffolding

Run shopify app generate extension and select the discount function type you need. The CLI sets up the project structure, schema files, and the GraphQL input query.

The input query is the most important part of the function. It defines what cart and customer data Shopify will pass to your function. Get this right early, because changing it later means redeploying.

Writing the function

The function takes the GraphQL input as a typed structure and returns operations. Rust is the default and is faster. JavaScript works for most use cases and is easier for teams without Rust experience.

Same pattern we use with Cart Transform: write in JavaScript first, profile execution time under load, port to Rust only if approaching the 50ms ceiling. Most simple discount logic runs comfortably in JavaScript at 10-30ms.

Deployment and configuration

Functions deploy via shopify app deploy as part of an app. After deploy, you create discount records in the Shopify admin that reference the function. The merchant-facing config lives in metafields on the discount record.

This is the bit that catches new teams: a Function does not become "active" just by deploying. You have to create at least one Discount record in the admin that uses the Function. That Discount record can be automatic (always on for matching carts) or code-based (shopper enters a code).

Common patterns

Tiered discounts

"Spend $100, get 10% off. Spend $200, get 15% off. Spend $300, get 20% off."

Use Order Discount Function. Read cart subtotal from the input. Branch on threshold. Return the matching discount.

function run(input) {
  const subtotal = parseFloat(input.cart.cost.subtotalAmount.amount);
  let percentage = 0;

  if (subtotal >= 300) percentage = 20;
  else if (subtotal >= 200) percentage = 15;
  else if (subtotal >= 100) percentage = 10;
  else return { discounts: [] };

  return {
    discounts: [{
      message: percentage + "% off your order",
      targets: [{ orderSubtotal: { excludedVariantIds: [] } }],
      value: { percentage: { value: percentage } }
    }]
  };
}
BOGO (buy one get one)

"Buy two of product X, get the cheapest one free."

Use Product Discount Function. Count qualifying line quantities. Identify the lowest-priced unit. Return a 100% discount targeted at that specific unit.

BOGO is one of the most common reasons brands hit the 50ms ceiling in Functions. Counting and sorting cart lines can add up. Keep the logic tight.

Member pricing

"Customer tags include 'vip' get 15% off site-wide."

Use Order Discount Function (or Product if you only want specific lines discounted). Read customer tags from the input. Return the discount if the tag is present.

Pattern note: customer data is only available if the shopper is logged in. Guest checkout shoppers will not have customer tags in the input. Plan the discount logic accordingly.

Dynamic bundle discount

"Add three items from this collection, get 25% off all three."

Use Product Discount Function paired with a Cart Transform Function. Cart Transform handles the bundle definition (merging or expanding lines). Discount Function applies the bundle pricing.

For the Cart Transform side, see our Cart Transform Functions walkthrough. The two function types are designed to work together: Cart Transform runs first, Discount Function runs second.

Segment-specific offers

"First-time customers get 20% off their first order."

Use Order Discount Function. Read customer.numberOfOrders from the input. Return the discount only if it equals 0 (or 1, depending on whether you want to include the current order).

Performance characteristics

Same hard limits as other Functions:

  • Execution time: 50ms per invocation
  • Input size: 5MB max (effectively unlimited for normal cart sizes)
  • Output size: 256KB max operations
  • Network calls: none allowed, all data must come from the GraphQL input
  • Total functions per shop: 25 across all function types combined

The 25-function ceiling is the one that bites brands running both Cart Transform and Discount functions plus a few Checkout extensions. Plan the function budget early.

Limits and gotchas

Order of execution

Discount Functions run after Cart Transform Functions, in the order: Cart Transform → Discount Function → Shopify discount engine. Your function output goes into Shopify's discount pipeline, which then applies eligibility rules, combinability, and stacking.

Discount combinability

Shopify enforces combinability rules: an Order Discount can stack with a Shipping Discount but not with another Order Discount (in most cases). Your Function output is subject to these rules. Returning multiple operations does not mean all of them will apply.

Manual discount codes vs automatic

A Function can be triggered by an automatic discount (always on) or a code (shopper enters a code). The Function code is identical; the trigger lives on the Discount record in the admin. Same function, different activation modes.

Discount record limits

Shopify has separate limits on the number of Automatic Discounts and Discount Codes per shop. Functions do not bypass those limits. If you are running dozens of campaigns, plan the discount record budget.

Testing on a dev store

Functions deploy via the CLI and install via apps. On a dev store, you can test the full flow end to end. Always test on a dev store before pushing the app to production.

Migration from Shopify Scripts

Most Scripts can be ported to Functions. The mapping is roughly:

  • Line Item Script → Product Discount Function
  • Shipping Script → Shipping Discount Function
  • Payment Script → no direct equivalent (covered partly by Checkout Extensibility)

Patterns to watch:

  • Scripts that use external API data (via metafield hacks) need to move that data into product or customer metafields, since Functions cannot make outbound calls
  • Scripts that do cart line modifications belong in Cart Transform Functions, not Discount Functions
  • Scripts that depend on multi-step conditional logic should be rewritten cleanly rather than ported line-by-line

If you are still on Scripts, start the migration audit now. The deprecation timeline gets less forgiving each quarter. For the broader Shopify Functions picture, our Cart Transform Functions walkthrough covers the cart-side surface, and our Checkout Extensibility API walkthrough covers the checkout surface.

Where to start

One: audit your existing Scripts. List every Script in production, what it does, and which Function type it maps to. Most brands find a handful of zombie Scripts no one remembers writing. Cut those first.

Two: pick the simplest Discount Function for migration. Tiered Order Discount is usually the easiest first port: clear logic, one function type, no Cart Transform dependency.

Three: build the input GraphQL query first. Spend an hour getting it right. The query defines the data shape your function logic operates on, and rewriting it later is painful.

Four: test exhaustively on a dev store before deploying to production. Discount logic that fires wrong on day one of a campaign can refund a lot of money to a lot of customers.

If you want help scoping a Scripts-to-Functions migration or building custom discount logic, book a technical review. We will audit your current discount stack and tell you what to migrate, what to retire, and what to rebuild from scratch.

Are Shopify Scripts being deprecated?

Yes. Shopify announced the deprecation path in 2023. Plus brands are being migrated to Functions over a multi-year runway, with Functions being the supported path forward for all discount and promotion logic.

What is the difference between a Discount Function and a Cart Transform Function?

Cart Transform modifies cart contents (adds, merges, removes line items, changes line pricing). Discount Functions apply discounts to lines, orders, or shipping. They can run together: Cart Transform first, Discount Function second.

Can a Discount Function read customer data?

Yes, if the shopper is logged in. The GraphQL input can include customer tags, order history, and metafields. Guest shoppers will not have customer data in the input.

Rust or JavaScript for Discount Functions?

JavaScript is faster to write and easier to maintain. Rust is faster at runtime. Most discount logic runs comfortably in JavaScript at 10-30ms. Port to Rust only if approaching the 50ms ceiling.

How many Functions can a Shopify Plus store run at once?

25 total across all function types (Cart Transform, Product Discount, Order Discount, Shipping Discount, plus other function types like Delivery Customization). Plan the function budget early if you are running multiple campaigns and bundle logic.

Can I trigger a Discount Function with a code?

Yes. Functions can be triggered by an automatic discount (always on for matching carts) or by a code (shopper enters the code at checkout). The function code is the same; the trigger is configured on the Discount record in the admin.

What happens if my Discount Function exceeds 50ms?

Shopify kills the execution and the discount does not apply. The cart proceeds without your modification. There is no partial-execution fallback.

How long does a Scripts-to-Functions migration take?

Depends on the complexity of your Scripts stack. A typical Plus brand with 5-10 Scripts ports in 4-8 weeks including testing. Brands with complex multi-Script logic can take 12-16 weeks.

A Shopify Plus Agency for Strategic Design & Advanced Engineering

Building something ambitious?

TLDR Summary
  • Shopify Functions for Discounts run server-side on Shopify infrastructure. Sub-50ms execution. Rust or JavaScript.
  • Three Discount Function types: Product Discount, Order Discount, and Shipping Discount. Each covers a different surface of the discount stack.
  • Functions replace most Shopify Scripts use cases. Scripts is on the deprecation path and Plus brands should be planning migration now.
  • Common patterns: tiered discounts, BOGO logic, member pricing, dynamic bundle discounts, segment-specific offers.
  • Hard limits: 50ms execution, 5MB input, 256KB output, no outbound HTTP, max 25 functions per shop across all function types.
  • Best paired with Cart Transform Functions when discount logic depends on cart-side modifications (bundles, line merging, dynamic pricing).
<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Shopify Functions for Discounts and Promotions","description":"A technical walkthrough of Shopify Functions for Discounts on Plus. Function types, dev workflow, code patterns, limits, and Scripts migration.","datePublished":"2026-08-07","author":{"@type":"Person","name":"Max Karlsson"},"publisher":{"@type":"Organization","name":"Flux Agency","url":"https://www.fluxagency.com"},"mainEntityOfPage":{"@type":"WebPage","@id":"https://www.fluxagency.com/insights/shopify-functions-discounts-promotions"}}</script>