Talk Shop
Home
Learn More
About Us
Follow Us
Blog
Tools
Newsletter
Join Discord
Join

Community

  • Developers
  • Growth
  • Entrepreneurs
  • Support
  • Experts
  • Tools

Location

123 Mars, Crater City, Red Planet

(WiFi may be spotty)

Hours

Who has time for breaks? We're here 24/7!

Contact

hello@letstalkshop.com

Talk Shop
Talk Shop

Built for real builders. Not affiliated with Shopify Inc.

Home
Privacy
Terms
  1. Home
  2. >Blog
  3. >Shopify Development
  4. >Shopify Functions Tutorial: Build Your First Custom Discount (2026)
Shopify Development12 min read

Shopify Functions Tutorial: Build Your First Custom Discount (2026)

Step-by-step tutorial to build your first custom discount with Shopify Functions. Covers setup, Rust and JavaScript, deployment, and common mistakes.

Talk Shop

Talk Shop

Apr 3, 2026

Shopify Functions Tutorial: Build Your First Custom Discount (2026)

In this article

  • Why Every Shopify Developer Needs to Learn Functions Right Now
  • What Are Shopify Functions and How Do They Work?
  • Prerequisites: Setting Up Your Environment
  • Scaffolding Your Shopify Functions App
  • Writing Your First Discount Function
  • Building the Merchant Configuration UI
  • Testing Your Shopify Functions Discount Locally
  • Deploying Your Custom Discount to a Live Store
  • Discount Application Strategies and Stacking
  • Common Mistakes When Building Shopify Functions
  • Advanced Patterns: Beyond Basic Discounts
  • Performance Optimization and Monitoring
  • Shopify Functions Tutorial: Your Next Steps

Why Every Shopify Developer Needs to Learn Functions Right Now

Shopify Scripts officially stops executing on June 30, 2026. If you're still relying on Scripts for custom discount logic, the clock is already ticking — no new Scripts or edits are allowed after April 15, 2026, according to Shopify's deprecation timeline.

The replacement? Shopify Functions — server-side WebAssembly modules that run directly on Shopify's global infrastructure in under 5 milliseconds. They're faster, more powerful, and available across every purchase surface from checkout to POS.

This Shopify Functions tutorial walks you through building your first custom discount from scratch. You'll set up your development environment, write the function logic, configure the merchant-facing UI, test locally, and deploy to a live store. Whether you pick Rust or JavaScript, you'll have a working discount function by the end. If you're new to Shopify app development, this guide assumes basic familiarity with the Shopify CLI and Partner Dashboard.

What Are Shopify Functions and How Do They Work?

Shopify Functions are small, sandboxed programs that run inside Shopify's infrastructure during critical purchase flows. Unlike app proxies or webhooks that add network latency, Functions execute inline — right alongside Shopify's own checkout logic.

Every Function follows the same three-step model:

  1. Input — Shopify sends your function a JSON payload defined by a GraphQL query you write. You choose exactly which cart, product, and customer data you need.
  2. Logic — Your code processes that input and makes decisions. This runs as a compiled WebAssembly (Wasm) module.
  3. Output — Your function returns a structured JSON response that tells Shopify what to do (apply a discount, reject a payment method, etc.).

Where Functions Run

Functions aren't hosted on your server. Shopify compiles your code to WebAssembly, uploads it to their edge infrastructure, and executes it within the checkout pipeline. This means zero cold starts and consistent sub-5ms execution times, according to Shopify's Functions documentation.

What Functions Can Do

Discounts are the most common use case, but Functions power seven different extension points:

  • Discounts — product, order, and shipping discounts
  • Payment customizations — hide, rename, or reorder payment methods
  • Delivery customizations — modify shipping options at checkout
  • Cart validation — enforce rules before checkout completion
  • Order routing — control fulfillment location logic
  • Bundles — create custom product bundles
  • Fulfillment constraints — set delivery promises and constraints

For this tutorial, we're focused on the discount API — the most practical starting point for most developers.

Prerequisites: Setting Up Your Environment

Focused shot of a monitor displaying a glowing code interface.

Before writing any code, you need three things configured on your machine.

Developer Accounts and Tools

RequirementDetails
Shopify Partner accountFree at partners.shopify.com
Development storeCreate one from your Partner Dashboard (free, no subscription needed)
Node.js 22+Required for the Shopify CLI
Shopify CLIInstall via npm install -g @shopify/cli
Rust toolchainIf using Rust: install via rustup.rs, then add the Wasm target

If you're choosing Rust (recommended for production), add the WebAssembly compilation target:

bashbash
rustup target add wasm32-unknown-unknown

For JavaScript, no additional toolchain is needed — the Shopify CLI handles compilation through Javy (a QuickJS-based Wasm compiler) automatically.

Choosing Between Rust and JavaScript

This is the first architectural decision you'll face, and it matters more than you might think.

FactorRustJavaScript
PerformanceFastest — compiles directly to Wasm~3x slower than Rust equivalent
Instruction budgetUses far fewer of the 11M instruction limitCan hit limits with complex logic
Learning curveSteeper if you're new to RustFamiliar syntax for web developers
Best forProduction apps, complex logic, public appsPrototyping, simple discounts, custom apps
Binary sizeSmaller compiled output~220 bytes + bytecode via dynamic linking

One developer documented a 7x reduction in instruction count after migrating a discount function from JavaScript to Rust. For prototyping, JavaScript is perfectly fine. For anything headed to the Shopify App Store, strongly consider Rust.

Scaffolding Your Shopify Functions App

Let's build. Start by creating a new Shopify app if you don't already have one:

bashbash
npm init @shopify/app@latest

Follow the prompts to name your app and select your preferred framework. Once the app is created, navigate into the project directory and generate a discount function extension:

bashbash
cd your-app-name
shopify app generate extension --template discount --name my-first-discount

The CLI scaffolds everything you need inside extensions/my-first-discount/. Here's what gets created:

texttext
extensions/my-first-discount/
├── shopify.extension.toml              # Extension configuration
├── src/
│   ├── cart_lines_discounts_generate_run.graphql   # Input query
│   ├── cart_lines_discounts_generate_run.rs        # Function logic (Rust)
│   └── ...
├── Cargo.toml                          # Rust dependencies (if using Rust)
└── input.graphql                       # Alternative input query location

Understanding the Configuration File

Open shopify.extension.toml — this is where Shopify learns what your function does:

tomltoml
api_version = "2026-04"

[[extensions]]
name = "my-first-discount"
handle = "my-first-discount"
type = "function"

[[extensions.targeting]]
target = "cart.lines.discounts.generate.run"
input_query = "src/cart_lines_discounts_generate_run.graphql"
export = "run"

The target field tells Shopify which extension point your function plugs into. For product and order discounts, use cart.lines.discounts.generate.run. For shipping discounts, you'd add a second targeting block with cart.delivery-options.discounts.generate.run.

Writing Your First Discount Function

Isometric view of server modules connected by a line of light.

Now for the core logic. We'll build a "spend $100, get 10% off" automatic discount — simple enough to understand the architecture, complex enough to be useful.

Step 1: Define Your Input Query

Open the GraphQL input query file and specify exactly what data your function needs from the cart:

graphqlgraphql
query RunInput {
  cart {
    cost {
      subtotalAmount {
        amount
        currencyCode
      }
    }
    lines {
      id
      quantity
      merchandise {
        ... on ProductVariant {
          id
          product {
            title
          }
        }
      }
      cost {
        totalAmount {
          amount
        }
      }
    }
  }
  discountNode {
    metafield(namespace: "my-discount", key: "config") {
      value
    }
  }
}

Only query what you actually need. Every extra field costs deserialization instructions against your 11-million instruction budget. This is one of the most common performance mistakes new developers make.

Step 2: Write the Function Logic (Rust)

Here's the complete Rust implementation:

rustrust
use shopify_function::prelude::*;
use shopify_function::run_function_with_input;

#[typeshare]
#[derive(Deserialize)]
pub struct Config {
    pub minimum_amount: f64,
    pub discount_percentage: f64,
}

#[shopify_function]
fn run(input: RunInput) -> FunctionRunResult {
    let config: Config = input
        .discount_node
        .metafield
        .as_ref()
        .map(|m| serde_json::from_str(&m.value).unwrap())
        .unwrap_or(Config {
            minimum_amount: 100.0,
            discount_percentage: 10.0,
        });

    let cart_total: f64 = input
        .cart
        .cost
        .subtotal_amount
        .amount
        .parse()
        .unwrap_or(0.0);

    if cart_total < config.minimum_amount {
        return Ok(FunctionRunResult {
            discount_application_strategy: DiscountApplicationStrategy::FIRST,
            discounts: vec![],
        });
    }

    let targets: Vec<Target> = input
        .cart
        .lines
        .iter()
        .map(|line| Target::OrderSubtotal {
            excluded_variant_ids: vec![],
        })
        .collect();

    Ok(FunctionRunResult {
        discount_application_strategy: DiscountApplicationStrategy::FIRST,
        discounts: vec![Discount {
            message: Some(format!(
                "Spend ${:.0}+ and save {:.0}%!",
                config.minimum_amount, config.discount_percentage
            )),
            targets,
            value: Value::Percentage(Percentage {
                value: config.discount_percentage.to_string(),
            }),
            conditions: None,
        }],
    })
}

Step 3: Write the Function Logic (JavaScript Alternative)

If you're prototyping in JavaScript, here's the equivalent:

javascriptjavascript
// @ts-check

/**
 * @param {RunInput} input
 * @returns {FunctionRunResult}
 */
export function run(input) {
  const config = JSON.parse(
    input?.discountNode?.metafield?.value ?? '{}'
  );

  const minimumAmount = config.minimumAmount ?? 100;
  const discountPercentage = config.discountPercentage ?? 10;

  const cartTotal = parseFloat(input?.cart?.cost?.subtotalAmount?.amount ?? '0');

  if (cartTotal < minimumAmount) {
    return { discountApplicationStrategy: 'FIRST', discounts: [] };
  }

  return {
    discountApplicationStrategy: 'FIRST',
    discounts: [
      {
        message: `Spend $${minimumAmount}+ and save ${discountPercentage}%!`,
        targets: [{ orderSubtotal: { excludedVariantIds: [] } }],
        value: {
          percentage: { value: discountPercentage.toString() },
        },
      },
    ],
  };
}

Important JavaScript limitation: Javy compiles ES2020 syntax but provides no event loop. Do not use async/await, fetch, setTimeout, or any Node.js/browser APIs — they compile but throw runtime errors, as detailed in Shopify's JavaScript for Functions guide.

Building the Merchant Configuration UI

Tablet displaying a blank configuration interface on a dark surface.

Your function logic is only half the equation. Merchants need a way to configure the discount — setting the minimum spend threshold and discount percentage — without touching code.

Shopify Functions communicate with their configuration UI through metafields. You build an Admin UI extension that saves settings to a metafield, and your function reads that metafield at runtime.

Creating the UI Extension

Generate an Admin UI extension alongside your function:

bashbash
shopify app generate extension --template discount_ui --name my-discount-ui

This creates a React-based UI using Shopify's Remote UI components. The key components you'll use:

  • <BlockStack> — vertical layout
  • <TextField> — number inputs for threshold and percentage
  • <Text> — labels and descriptions
  • <Banner> — validation messages

The UI extension reads and writes to the same metafield namespace (my-discount, key config) that your function queries in its GraphQL input. When a merchant saves the configuration, Shopify stores it as a JSON string in the metafield. When the function executes, it deserializes that string back into your config struct.

For the full UI extension implementation pattern, refer to Shopify's guide on building discount UI extensions.

Testing Your Shopify Functions Discount Locally

Testing Functions locally is critical. You don't want to deploy to a store just to discover your logic has a bug.

Using Function Replay

The Shopify CLI includes a replay tool that lets you execute your function against real cart data captured from your development store:

bashbash
shopify app function replay

This command:

  1. Pulls recent function execution logs from your dev store
  2. Lets you select a specific execution
  3. Reruns your function locally with that exact input
  4. Shows you the output so you can verify correctness

Manual Testing with function-runner

For more control, use the function-runner tool directly:

bashbash
npx function-runner -f 'dist/function.wasm' -e run -i 'test-input.json'

Create a test-input.json file that mimics a real cart payload:

jsonjson
{
  "cart": {
    "cost": {
      "subtotalAmount": { "amount": "150.00", "currencyCode": "USD" }
    },
    "lines": [
      {
        "id": "gid://shopify/CartLine/1",
        "quantity": 2,
        "merchandise": {
          "__typename": "ProductVariant",
          "id": "gid://shopify/ProductVariant/123",
          "product": { "title": "Test Product" }
        },
        "cost": { "totalAmount": { "amount": "150.00" } }
      }
    ]
  },
  "discountNode": {
    "metafield": {
      "value": "{\"minimumAmount\": 100, \"discountPercentage\": 10}"
    }
  }
}

What to Test

ScenarioExpected Result
Cart total below threshold ($50)No discount applied, empty discounts array
Cart total at threshold ($100)10% discount applied
Cart total above threshold ($200)10% discount applied
Missing metafield configFalls back to default values ($100/10%)
Empty cartNo discount, no errors

Test edge cases aggressively. When a function exceeds its instruction limit or throws an error in production, it fails silently — the discount simply doesn't appear, with no error message shown to the customer.

Deploying Your Custom Discount to a Live Store

Once your function passes local testing, deployment is a two-step process.

Step 1: Deploy the App

bashbash
shopify app deploy

This compiles your function to WebAssembly, uploads it to Shopify's infrastructure, and registers the extension. The CLI handles versioning — each deployment creates a new version of your app.

Step 2: Activate the Discount

After deployment, create the actual discount in your store:

  1. Go to Shopify Admin > Discounts > Create discount
  2. Select your custom discount type (it appears under the app name)
  3. Configure the settings through your UI extension
  4. Set the discount to Active

Alternatively, create the discount programmatically via the GraphQL Admin API:

graphqlgraphql
mutation {
  discountAutomaticAppCreate(
    automaticAppDiscount: {
      title: "Spend $100, Get 10% Off"
      functionId: "your-function-id"
      startsAt: "2026-04-03T00:00:00Z"
      metafields: [
        {
          namespace: "my-discount"
          key: "config"
          type: "json"
          value: "{\"minimumAmount\":100,\"discountPercentage\":10}"
        }
      ]
    }
  ) {
    automaticAppDiscount {
      discountId
    }
    userErrors {
      field
      message
    }
  }
}

For merchants on Shopify Plus, custom app functions unlock the most flexibility. Public app functions work on all Shopify plans.

Discount Application Strategies and Stacking

Laptop screen split vertically by electric blue light.

One of the most misunderstood aspects of Shopify Functions is how multiple discounts interact. Understanding this correctly prevents hours of debugging.

The discountApplicationStrategy Field

Your function's output includes a discountApplicationStrategy field with three options:

StrategyBehavior
FIRSTApplies the first qualifying discount and ignores the rest
MAXIMUMApplies whichever single discount gives the customer the biggest savings
ALLApplies every qualifying discount (they stack)

Critical distinction: This strategy controls how discount candidates within a single function compete. It does not control how your function's discounts interact with other discount functions or manual discounts.

Cross-Function Stacking

Global discount stacking is controlled by the combinesWith property on the discount node — not the application strategy. A single checkout can run up to 25 discount functions concurrently, each operating in isolation with no knowledge of the others, as documented in the Shopify Discount Function API reference.

Confusing selection strategies with stacking rules is one of the most common mistakes developers make. Your function decides which of its own discount candidates to propose. Shopify's discount engine then decides which proposed discounts from all functions actually apply based on combination rules.

Common Mistakes When Building Shopify Functions

After working with Functions and studying developer experiences across the ecosystem, these are the pitfalls that catch most builders.

Performance Mistakes

  • Querying unnecessary input fields — Every GraphQL field you request costs deserialization instructions. A developer profiled their function and found JSON serialization alone consumed 5.94 million of their 11 million instruction budget. Trim your input query ruthlessly.
  • Starting with JavaScript for complex logic — JS works great for simple discounts. But if your function processes large catalogs or runs complex calculations, you'll hit the instruction limit fast. The Discount Kit team saw a 30% drop in instruction counts after optimizing their Wasm approach.
  • Ignoring the 256KB module size limit — Especially relevant for Rust developers pulling in large crate dependencies. Keep your dependency tree lean.

Architectural Mistakes

  • Confusing Cart Transform with Discount Functions — Cart Transform changes cart structure and presentation. Discount Functions change pricing. Mixing responsibilities creates brittle, hard-to-debug behavior.
  • Not handling missing metafields — If a merchant deletes or corrupts the configuration metafield, your function should fall back gracefully, not crash.
  • Skipping monitoring — Functions fail silently. Set up monitoring through Partner Dashboard > Apps > Insights > Functions to catch instruction limit overruns and errors before merchants report them.

Migration Mistakes

MistakeConsequencePrevention
Delaying Scripts migrationStore breaks after June 30, 2026Start migrating now — no edits allowed after April 15, 2026
1:1 porting Script logicBloated, inefficient FunctionsRedesign logic for the input/output model
Not testing with real cart dataUnexpected edge cases in productionUse shopify app function replay with real executions
Hardcoding configurationRequires redeployment for every changeUse metafields for merchant-configurable settings

Advanced Patterns: Beyond Basic Discounts

Once you're comfortable with the fundamentals, these patterns unlock more sophisticated discount logic.

Tiered Volume Discounts

Instead of a flat threshold, implement progressive tiers — 5% off at $50, 10% at $100, 15% at $200. Parse the tiers from your metafield configuration and iterate through them to find the highest qualifying tier.

Customer-Specific Discounts

Query cart.buyerIdentity in your input to access customer tags and metafields. This lets you build loyalty-tier discounts, wholesale pricing, or first-time buyer promotions — all running natively in the checkout without app proxy latency.

Product-Targeted Discounts

Instead of targeting orderSubtotal, target specific product variants using productVariant targets. This lets you run "Buy 2 of Product X, get 20% off" promotions that only discount the qualifying items.

For working code examples of all these patterns, explore the Shopify Function Examples repository on GitHub, which includes discount implementations in both Rust and JavaScript.

Combining Multiple Discount Classes

A single function can target product discounts, order discounts, and shipping discounts simultaneously by defining multiple targeting blocks in your shopify.extension.toml. This reduces the number of functions you need to maintain and keeps related logic together.

Performance Optimization and Monitoring

Macro view of light moving along a circuit board path.

Shopify Functions run in a highly constrained environment. Optimizing performance isn't optional — it's a requirement for production reliability.

Instruction Budget Management

Your function gets 11 million instructions per execution. Here's how to stay well within budget:

  • Profile locally — Run npx function-runner with the --stats flag to see exact instruction counts
  • Minimize input query — Only request GraphQL fields your logic actually reads
  • Avoid large allocations — Pre-allocate vectors in Rust; avoid building large intermediate arrays in JavaScript
  • Use early returns — If the cart doesn't qualify, return an empty discounts array immediately without processing line items

Monitoring in Production

After deployment, monitor your function's health through the Partner Dashboard:

  1. Navigate to Apps > Your App > Insights > Functions
  2. Check execution success rate — anything below 99.9% needs investigation
  3. Review instruction count distribution — p50, p75, and p99 values
  4. Watch for instruction limit errors — these mean your function hit the 11M cap and failed silently

The Shopify engineering team's deep dive into JavaScript WebAssembly performance provides excellent benchmarks for setting performance expectations. JavaScript functions typically complete well within the 5ms window for realistic use cases, but watch instruction counts carefully as cart complexity grows.

Shopify Functions Tutorial: Your Next Steps

You've just walked through the complete lifecycle of building your first custom discount with Shopify Functions — from environment setup through deployment and monitoring. The input-logic-output model is straightforward once you've built your first function, and the patterns scale naturally to more complex discount scenarios.

Here's your action plan:

  1. Set up your environment today — Partner account, dev store, Shopify CLI, and your chosen language toolchain
  2. Build the spend-threshold discount from this tutorial as your learning project
  3. Test with real cart data using shopify app function replay before deploying
  4. Monitor instruction counts in the Partner Dashboard after going live
  5. Migrate any existing Scripts before the April 15, 2026 edit freeze

If you're building on Shopify and want to connect with other developers tackling Functions, app development, and checkout customization, join the Talk Shop community where Shopify developers share implementation patterns and debug production issues together.

What discount logic are you planning to build with Shopify Functions? Whether it's tiered pricing, customer-specific offers, or bundle discounts, the architecture you learned here applies to all of them. Start with the simplest version, get it deployed, and iterate from there.

Shopify DevelopmentApps & Integrations
Talk Shop

About Talk Shop

The Talk Shop team — insights from our community of Shopify developers, merchants, and experts.

Related Insights

Related

Shopify Discord Bots for Store Automation (2026)

Related

Shopify Wishlist Feature: Setup and Strategy Guide (2026)

The ecommerce newsletter that's actually useful.

Daily trends, teardowns, and tactics from the top 1% of ecommerce brands. Delivered every morning.

No spam. Unsubscribe anytime. · Learn more

New

Business Name Generator

Generate unique, brandable business names with AI. Check domain availability instantly.

Generate Names

Talk Shop Daily

Daily ecommerce news, teardowns, and tactics.

No spam. Unsubscribe anytime. · Learn more

Try our Business Name Generator