← Back to blog

How to Build a Shopify Discount Function from Scratch

Updated March 2026 · 20 min read

With Shopify Scripts officially deprecated, every store using Ruby-based discount scripts needs to migrate to Shopify Functions. This tutorial walks you through building a product discount Function from zero — or you can use ScriptShift to auto-generate it from your existing script.

Prerequisites

Step 1: Create the Function Extension

# In your Shopify app directory
shopify app generate extension --type product_discounts

# Choose TypeScript when prompted
# This creates:
#   extensions/product-discount/
#   ├── src/
#   │   └── run.ts          # Your discount logic
#   ├── input.graphql       # What data you need from Shopify
#   ├── shopify.extension.toml
#   └── package.json

Step 2: Define Your Input Query

The input.graphql file tells Shopify what data to send to your Function. For a tiered discount based on quantity:

query RunInput {
  cart {
    lines {
      quantity
      merchandise {
        ... on ProductVariant {
          id
          product {
            id
            title
            hasAnyTag(tags: ["bulk-eligible"])
          }
        }
      }
      cost {
        amountPerQuantity {
          amount
          currencyCode
        }
      }
    }
  }
  discountNode {
    metafield(namespace: "discount-config", key: "tiers") {
      value
    }
  }
}

Step 3: Write the Discount Logic

Here's a complete tiered bulk discount Function in TypeScript. This replaces the classic Ruby tiered discount script:

// src/run.ts
import type {
  RunInput,
  FunctionRunResult,
  Target,
  Discount,
} from "../generated/api";

interface Tier {
  threshold: number;
  percentage: number;
  message: string;
}

const DEFAULT_TIERS: Tier[] = [
  { threshold: 10, percentage: 20, message: "20% bulk discount" },
  { threshold: 5, percentage: 10, message: "10% bulk discount" },
  { threshold: 3, percentage: 5, message: "5% bulk discount" },
];

export function run(input: RunInput): FunctionRunResult {
  // Read tiers from metafield config, or use defaults
  let tiers = DEFAULT_TIERS;
  const metafieldValue = input.discountNode.metafield?.value;
  if (metafieldValue) {
    try {
      tiers = JSON.parse(metafieldValue);
    } catch {
      // Use defaults if metafield is invalid
    }
  }

  // Sort tiers by threshold descending (highest first)
  tiers.sort((a, b) => b.threshold - a.threshold);

  const discounts: { targets: Target[]; value: Discount; message: string }[] = [];

  for (const line of input.cart.lines) {
    const variant = line.merchandise;
    if (variant.__typename !== "ProductVariant") continue;

    // Find applicable tier
    const tier = tiers.find((t) => line.quantity >= t.threshold);
    if (!tier) continue;

    discounts.push({
      targets: [
        {
          productVariant: { id: variant.id },
        },
      ],
      value: {
        percentage: { value: tier.percentage.toString() },
      },
      message: tier.message,
    });
  }

  if (discounts.length === 0) {
    return { discounts: [], discountApplicationStrategy: "FIRST" };
  }

  return {
    discounts,
    discountApplicationStrategy: "FIRST",
  };
}

Step 4: Configure the Extension

# shopify.extension.toml
api_version = "2025-04"

[[extensions]]
name = "Tiered Bulk Discount"
handle = "tiered-bulk-discount"
type = "product_discounts"

  [extensions.build]
  command = "npm run build"
  path = "dist/function.wasm"

  [extensions.ui]
  enable = true
  handle = "tiered-bulk-discount-ui"

Step 5: Test and Deploy

# Build and test locally
npm run build
shopify app function run --export run

# Deploy to your dev store
shopify app deploy

# Create the discount in Shopify Admin:
# Settings → Discounts → Create → Automatic discount
# Choose your "Tiered Bulk Discount" function

Common Gotchas

The Easier Way

If you have an existing Ruby script and just want working Function code, ScriptShift can analyze your script and generate the complete Function project — including the input query, TypeScript logic, TOML config, and test files. Free analysis, instant results.

Migrate automatically

Paste your Ruby script → get working Shopify Functions code in seconds.

Try ScriptShift Free →