The post-Scripts dilemma: moving discounts to WebAssembly
For nearly a decade, enterprise Shopify Plus stores handled complex promotions using the Ruby Script Editor. Scripts allowed developers to inspect line items and apply arbitrary price overrides directly within the checkout execution thread.
However, Scripts suffered from severe limitations: Ruby runtime interpretation added latency to checkout, execution timeouts caused silent failures, and script logic was isolated from standard GraphQL APIs.
Under Checkout Extensibility, Shopify replaced Ruby scripts with Shopify Functions. Instead of running on an interpreted server-side script engine, Functions compile to WebAssembly and run inside an isolated, ultra-secure sandbox managed directly by Shopify's core checkout pipeline.
In our checkout engineering for Forged 4x4, we designed custom checkout logic to execute within these platform boundaries, eliminating the latency of third-party discount apps while guaranteeing deterministic price execution.
- Strict 11-million instruction ceiling: Functions must execute in under 5 milliseconds
- 128 KB memory budget: Prevents memory leaks and heavy runtime allocations
- Zero network access: All calculation inputs must be provided in GraphQL RunInput
- Deterministic pure execution: Guarantees identical outputs given identical inputs
The mechanics of purchase.product-discount.run
The purchase.product-discount.run target executes whenever a buyer loads the cart, updates line quantities, or proceeds through checkout.
First, Shopify executes your function's configured GraphQL query against the live checkout context, fetching cart lines, quantities, variant IDs, parent product GIDs, and configured discount metafields.
Next, Shopify feeds the serialised JSON payload into your compiled WebAssembly module. The function aggregates volume pools, qualifies quantity tiers, applies margin protection caps, and prepares line item targets.
Finally, the function returns a structured FunctionResult payload containing an array of ProductDiscount objects and a declared discountApplicationStrategy. Because calculation happens inside the checkout core rather than on the client's browser, there is zero layout shift, no flash of un-discounted prices, and zero exposure to client-side DOM tampering.
Variant family pooling: aggregating quantities across related SKUs
A common limitation of standard discount apps is that volume pricing is tied strictly to individual variant IDs. If a merchant sells lug nuts in packs of 4 with options for Thread Pitch (M12 vs M14) and Finish (Black vs Chrome), a buyer purchasing one pack of each option is penalised with list pricing, even though they purchased 8 total units from the same product family.
To provide a modern B2B or consumer experience, volume pricing rules must pool line item quantities based on shared attributes: parent product GID, collection membership, or shared custom metafields.
When pooling across lines, the function evaluates the sum of all qualifying line quantities against the discount tier threshold, and then distributes the resulting unit markdown proportionally across each individual variant line item.
import assert from "node:assert/strict";
export type DiscountType = "PERCENTAGE" | "FIXED_AMOUNT_PER_UNIT";
export type GroupingStrategy = "PARENT_PRODUCT" | "COLLECTION" | "LINE_ITEM";
export interface VolumeDiscountTier {
minQuantity: number;
discountType: DiscountType;
discountValue: number;
}
export interface VolumeDiscountRule {
ruleId: string;
title: string;
groupingStrategy: GroupingStrategy;
tiers: VolumeDiscountTier[];
maxDiscountCapPerUnitCents?: number;
minMarginFloorCents?: number;
allowStackingWithDiscountCodes: boolean;
qualifyingProductParentIds?: string[];
}
export interface CartLineItem {
id: string;
merchandiseId: string;
productParentId: string;
quantity: number;
costPerUnitCents: number;
appliedDiscountCodes?: string[];
isB2BPriceList?: boolean;
}
export interface ProductDiscountRunInput {
cartLines: CartLineItem[];
discountRules: VolumeDiscountRule[];
cartDiscountCodes?: string[];
}
export interface ProductDiscountCandidate {
message: string;
targets: Array<{ cartLine: { id: string; quantity?: number } }>;
value: {
fixedAmount?: { amount: string; currencyCode: string };
percentage?: { value: number };
};
}
export interface ProductDiscountFunctionResult {
discountApplicationStrategy: "FIRST" | "MAXIMUM";
discounts: ProductDiscountCandidate[];
debugSummary: {
evaluatedLinesCount: number;
groupedPoolsCount: number;
discountsAppliedCount: number;
totalSavingsCents: number;
};
}
export function calculateTieredVolumeDiscount(
input: ProductDiscountRunInput,
currencyCode: string = "USD"
): ProductDiscountFunctionResult {
const { cartLines, discountRules, cartDiscountCodes = [] } = input;
const discounts: ProductDiscountCandidate[] = [];
let totalSavingsCents = 0;
let evaluatedLinesCount = 0;
let discountsAppliedCount = 0;
for (const rule of discountRules) {
if (!rule.allowStackingWithDiscountCodes && cartDiscountCodes.length > 0) {
continue;
}
const qualifyingLines = cartLines.filter((line) => {
if (line.isB2BPriceList) return false;
if (
rule.qualifyingProductParentIds &&
rule.qualifyingProductParentIds.length > 0 &&
!rule.qualifyingProductParentIds.includes(line.productParentId)
) {
return false;
}
return true;
});
evaluatedLinesCount += qualifyingLines.length;
const pools: Map<string, CartLineItem[]> = new Map();
for (const line of qualifyingLines) {
let poolKey = line.id;
if (rule.groupingStrategy === "PARENT_PRODUCT") {
poolKey = line.productParentId;
} else if (rule.groupingStrategy === "COLLECTION") {
poolKey = "COLLECTION_POOL";
}
const existing = pools.get(poolKey) ?? [];
existing.push(line);
pools.set(poolKey, existing);
}
for (const [poolKey, lines] of pools.entries()) {
const totalPoolQuantity = lines.reduce((sum, line) => sum + line.quantity, 0);
const sortedTiers = [...rule.tiers].sort((a, b) => b.minQuantity - a.minQuantity);
const eligibleTier = sortedTiers.find((tier) => totalPoolQuantity >= tier.minQuantity);
if (!eligibleTier) {
continue;
}
for (const line of lines) {
let discountPerUnitCents = 0;
if (eligibleTier.discountType === "PERCENTAGE") {
discountPerUnitCents = Math.round(line.costPerUnitCents * (eligibleTier.discountValue / 100));
} else {
discountPerUnitCents = eligibleTier.discountValue;
}
if (rule.minMarginFloorCents !== undefined) {
const maxAllowedDiscount = Math.max(0, line.costPerUnitCents - rule.minMarginFloorCents);
if (discountPerUnitCents > maxAllowedDiscount) {
discountPerUnitCents = maxAllowedDiscount;
}
}
if (rule.maxDiscountCapPerUnitCents !== undefined) {
if (discountPerUnitCents > rule.maxDiscountCapPerUnitCents) {
discountPerUnitCents = rule.maxDiscountCapPerUnitCents;
}
}
if (discountPerUnitCents <= 0) {
continue;
}
const lineTotalSavingsCents = discountPerUnitCents * line.quantity;
totalSavingsCents += lineTotalSavingsCents;
discountsAppliedCount++;
const decimalAmount = (discountPerUnitCents / 100).toFixed(2);
discounts.push({
message: `${rule.title} (Qty ${totalPoolQuantity}+)`,
targets: [
{
cartLine: {
id: line.id,
quantity: line.quantity,
},
},
],
value: {
fixedAmount: {
amount: decimalAmount,
currencyCode,
},
},
});
}
}
}
return {
discountApplicationStrategy: "FIRST",
discounts,
debugSummary: {
evaluatedLinesCount,
groupedPoolsCount: discountRules.length,
discountsAppliedCount,
totalSavingsCents,
},
};
}Stacking defense: the FIRST versus MAXIMUM strategy trap
When defining a Shopify Function discount result, developers must declare a discountApplicationStrategy: FIRST or MAXIMUM.
A common architectural trap occurs when merchants configure multiple overlapping discounts and rely on MAXIMUM. If a customer qualifies for both a 15% brand-wide promotional coupon and a 20% volume tier, MAXIMUM quietly discards the volume tier rules if the promotional coupon saves marginally more across other items.
Even more dangerous is uncontrolled discount compounding. If your Function does not explicitly inspect cartDiscountCodes in the RunInput, a customer entering a 20% newsletter voucher can stack that voucher on top of a 25% tiered volume discount, producing catastrophic negative-margin sales.
Our production engine inspects the incoming cart context: if non-combinable promotional coupon codes are present, the function automatically yields priority to the voucher or suppresses the volume tier, preserving the merchant's gross margin.
Margin preservation floors: guarding gross profitability
In specialized engineering, automotive, or industrial catalogs, margins fluctuate wildly between product lines: proprietary in-house hardware might carry a 60% gross margin, while third-party electronic winches carry a lean 18% margin.
Applying blanket volume discounts across an entire catalog guarantees that sourced hardware sells at a net cash loss.
To protect merchant solvency, custom Shopify Functions enforce margin preservation floors: absolute unit markdown caps (maxDiscountCapPerUnitCents), minimum net price floors (minMarginFloorCents), and wholesale B2B price list isolation.
When this needs an engineer
You do not need a custom Shopify Function if your promotional requirements can be handled by Shopify's native automatic discounts, standard collection discounts, or simple percentage sales that apply uniformly to single SKUs.
You need an engineer when your promotional logic outgrows platform defaults: when volume tiers must aggregate across complex variant families or parent product groupings; when custom discount algorithms must migrate from sunset Ruby Scripts without adding third-party app latency; when volume pricing must coordinate defensively with promotional coupons and B2B catalog price lists; and when strict margin protection floors must guard against unprofitable checkout compounding.
Explore our engineering work in Shopify Plus development, inspect our custom applications case study collection, review our work on Checkout Extensibility bundle expansion, or examine how custom checkout calculations were delivered in the Forged 4x4 case study.
Send us your discounting matrix, SKU grouping requirements, and margin constraints. We will architect a high-performance WebAssembly discount function that keeps your checkout fast, accurate, and profitable.
