AnyLearn
All lessons
Businessintermediate

Shopify Functions: Custom Logic Inside Checkout

Flow reacts after events; Functions participate during them. This lesson covers how Shopify Functions let you change discounts, shipping, payment options, and cart validation from inside checkout, why they run as WebAssembly compiled from JavaScript or Rust, and why that architecture replaced the old sandboxed Scripts.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 8

The gap Flow leaves

Lesson 1 ended at a wall. Flow reacts to events that have already happened, so it cannot answer questions like these:

  • "Wholesale customers buying 50 or more units get 15 percent off, calculated live in the cart."
  • "Hide express shipping for orders containing a hazardous item."
  • "Do not show cash on delivery to customers with a failed payment history."
  • "Block checkout if the cart mixes frozen goods with a pickup location that has no freezer."

Every one of these must happen while the customer is checking out, before the order exists. There is no event to react to yet, because the thing you want to influence is the thing in progress. Tagging the order afterward is useless; the customer already saw the wrong price.

This is the difference between reacting and participating. Flow watches the store and responds. Shopify Functions run inside the platform's own decision-making, at defined points in the commerce lifecycle, and change the answer the platform produces.

That is a genuinely different capability, and it raises an obvious engineering problem. You want merchant code executing in the middle of checkout, on Shopify's infrastructure, during the highest-stakes seconds of the whole business, on a platform serving enormous traffic. That code must be fast, safe, and unable to take anything down.

How you solve that problem determines the architecture, and it is why Functions look the way they do.

Full lesson text

All 8 steps on one page, for reading, reference, and search.

Show

1. The gap Flow leaves

Lesson 1 ended at a wall. Flow reacts to events that have already happened, so it cannot answer questions like these:

  • "Wholesale customers buying 50 or more units get 15 percent off, calculated live in the cart."
  • "Hide express shipping for orders containing a hazardous item."
  • "Do not show cash on delivery to customers with a failed payment history."
  • "Block checkout if the cart mixes frozen goods with a pickup location that has no freezer."

Every one of these must happen while the customer is checking out, before the order exists. There is no event to react to yet, because the thing you want to influence is the thing in progress. Tagging the order afterward is useless; the customer already saw the wrong price.

This is the difference between reacting and participating. Flow watches the store and responds. Shopify Functions run inside the platform's own decision-making, at defined points in the commerce lifecycle, and change the answer the platform produces.

That is a genuinely different capability, and it raises an obvious engineering problem. You want merchant code executing in the middle of checkout, on Shopify's infrastructure, during the highest-stakes seconds of the whole business, on a platform serving enormous traffic. That code must be fast, safe, and unable to take anything down.

How you solve that problem determines the architecture, and it is why Functions look the way they do.

2. What Scripts got wrong

Shopify's earlier answer was Scripts: merchant-written Ruby that ran in a separate sandboxed environment and could alter checkout behaviour. The idea was right and the architecture was not, and understanding why is the best way to understand Functions.

Scripts had four structural problems:

  • Latency. Running in a separate sandbox meant a network hop out and back during checkout. That cost time on the most time-sensitive page in commerce.
  • Timeouts under load. The failure mode was exactly backwards. During high-traffic events, precisely when a store most needs checkout to work, Scripts were slow to execute and prone to timing out.
  • Hard to debug. Problems surfaced in production, in a sandbox, during a sale.
  • Isolation from the platform. Scripts could not integrate with newer capabilities such as Flow, GraphQL, or Checkout UI Extensions. They were a walled-off feature rather than part of the ecosystem, which meant merchant logic lived in a place the rest of the platform could not see.

Shopify retired Scripts in mid-2026, and the direction of the replacement follows precisely from that list. If the problem is a slow, fragile hop into a separate environment, the fix is to stop leaving. Run the merchant's code inside Shopify's own infrastructure, at native speed, in a form that cannot misbehave.

That requirement, arbitrary third-party code running inside your infrastructure, fast, safely, without trusting it, describes a well-known technology.

3. Why WebAssembly

Shopify Functions are compiled WebAssembly modules that run directly inside Shopify's own infrastructure at key points in the commerce lifecycle. WebAssembly (Wasm) is a portable, low-level compilation target, and it is an unusually good fit here for reasons worth understanding, because they are the whole design.

  • It is sandboxed by construction. A Wasm module has no ambient access to the filesystem, the network, or memory outside its own. Shopify can run untrusted merchant code in-process without it reaching anything. The isolation is a property of the format, not of a separate environment you must hop to.
  • It is fast, and predictably so. Functions execute at sub-5-millisecond speeds, with no cold starts and consistent performance regardless of traffic volume. Re-read that against the Scripts failure list: it fixes latency, it fixes cold starts, and it fixes the collapse-under-load problem specifically.
  • It is language-agnostic. You write Functions in JavaScript, TypeScript, Rust, or AssemblyScript, and the Shopify CLI compiles your code to WebAssembly and deploys it as part of a Shopify app. Wasm is a compilation target, so the platform commits to one execution format while developers keep their language.
  • It is deterministic and bounded. A module gets input, computes, returns output. No hidden network calls to stall checkout.

The general lesson is bigger than Shopify. When a platform must run other people's code inside its own critical path, WebAssembly is the current best answer, because it gives isolation and native-ish speed simultaneously. Scripts had to choose between them and chose isolation, paying in latency.

4. What Functions can change

A Function does not run anywhere it likes. It plugs into defined extension points in the commerce lifecycle, each with its own API, input, and expected output. The current set includes:

Function APIWhat it decides
Discountwhat discount applies to this cart, computed live
Payment customizationwhich payment methods are shown, hidden, or reordered
Delivery customizationwhich shipping options are shown, hidden, or renamed
Cart and checkout validationwhether this cart is allowed to proceed
Order routingwhich location an order is fulfilled from

and the platform continues to add more.

The pattern is consistent and worth naming: Shopify decides where you may intervene, and you decide what happens there. A Function receives structured input about the cart, customer, and context, returns a structured decision, and Shopify applies it. You are not writing checkout; you are supplying the answer to one question checkout asks.

That constraint is deliberate, and it is what makes the whole thing safe. A Function cannot restructure the checkout flow, call your warehouse, or wander off. It answers a bounded question in bounded time. Compare this to the agentic-commerce pattern in a companion cursus: grant narrow, well-defined authority rather than general power. Same instinct, different domain.

Notice too that these extension points map exactly onto the examples that defeated Flow in the opening step. That is not a coincidence, they are the decisions merchants most want to influence, and each earned an API.

5. A Function in shape

The mental model is simplest as a pure function: input in, decision out.

// A discount Function, conceptually

input  = {
  cart: {
    lines: [ { quantity: 60, merchandise: {...} } ],
    buyerIdentity: { customer: { hasTags: ["wholesale"] } }
  }
}

run(input) {
  const units = sum(input.cart.lines.map(l => l.quantity))
  const isWholesale = input.cart.buyerIdentity
                        .customer.hasTags.includes("wholesale")

  if (isWholesale && units >= 50) {
    return { discounts: [ { value: { percentage: 15 } } ] }   // decision
  }
  return { discounts: [] }                                     // no change
}

Three things in that sketch matter more than the syntax.

It is pure. Input arrives, a decision is returned. No database call, no network request, no waiting. That is why it can promise sub-5ms with no cold starts: there is nothing to wait for. The Scripts failure mode is architecturally excluded rather than optimized away.

It reads a tag. hasTags: ["wholesale"] is Lesson 1's glue reappearing. A Flow workflow can tag the customer wholesale; a Function reads that tag at checkout. Flow classifies, Functions act on the classification. The two tools compose through shared state, exactly as predicted.

The merchant may never see this code. Functions are installed as part of an app and configured alongside other features directly in the Shopify admin, so merchants do not need to touch code to create or modify customizations. A developer ships the capability; the merchant sets the numbers.

6. The division of labour

Functions sharpen a distinction that runs through the whole platform: who builds the capability, and who operates it.

  • A developer writes the Function, compiles it via the Shopify CLI, and ships it inside an app. They decide what the logic can express: which inputs it reads, what shape of decision it returns.
  • A merchant installs the app and configures it in the admin. They decide the actual values: which tag, what percentage, which threshold.

This is why Functions arrive as apps rather than as a code box in the settings. Bundling logic into an app gives it a deployment path, versioning, and a maintainer, and lets one Function serve thousands of stores with different configuration. It also means checkout logic on a store is auditable: it comes from an installed app, not a snippet someone pasted in years ago and forgot.

That is a real improvement on the Scripts era, where checkout logic tended to be bespoke code living in one store, understood by one person, discovered only when it broke.

The practical guidance for a merchant follows from Lesson 1's rule, and it is worth stating plainly:

  • Need to react to something that happened? Flow. No code.
  • Need to change what checkout does while it happens? A Function, which means an app, which means a developer or an existing app from the ecosystem.

And check the ecosystem first. Common needs, tiered discounts, hiding payment methods, shipping rules, are already served by apps built on Functions. Writing your own is for logic genuinely specific to your business, which is a much smaller set than merchants assume.

7. Reacting versus participating

Put the two tools side by side, because the boundary is the useful thing to remember.

FlowFunctions
When it runsafter an eventduring the commerce lifecycle
Relationshipreacts to the storeparticipates in the decision
Built bymerchant, no codedeveloper, shipped in an app
Written ina visual builderJavaScript, TypeScript, Rust, AssemblyScript
Runs asShopify's workflow engineWebAssembly inside Shopify's infrastructure
Speed matters?not really, it is asynchronouscritically, sub-5ms in the checkout path
Typical usetag, notify, integratediscounts, shipping, payment, validation
Configured bythe merchant, directlythe merchant, in the admin

The unifying idea: Flow is about the store's history; Functions are about the store's decisions.

And they are stronger together than apart, which the tag example shows. Flow classifies customers into wholesale, VIP, or risky, writing that judgment into shared state over time. Functions read that state in the instant of checkout and act on it. Neither could do the other's job: Flow cannot participate in checkout, and a Function cannot watch your store for a week to decide who is a VIP.

What neither covers is everything outside Shopify's own model, external systems, bulk changes, and custom data, plus the question of how to choose when several approaches would work. That is Lesson 3.

8. Where a Function runs

A customer building a cart triggers Shopify's checkout to ask bounded questions, what discount, which shipping and payment options, is this cart valid, and the merchant's WebAssembly Function answers each in under five milliseconds inside Shopify's infrastructure, with Flow reacting only afterward.

flowchart TD
  A["Customer builds a cart"] --> B["Checkout asks: what discount applies?"]
  B --> C["Function runs as WebAssembly, in under 5ms"]
  C --> D["Returns a decision, no network calls"]
  D --> E["Shopify applies it to the cart"]
  E --> F["Checkout asks: which shipping and payment?"]
  F --> C
  E --> G["Order created"]
  G --> H["Flow reacts: tag, notify, integrate"]

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. What can Shopify Functions do that Flow structurally cannot?
    • Send emails to customers
    • Tag orders after they are placed
    • Participate in decisions during the commerce lifecycle, changing discounts, shipping options, payment methods, or cart validity before the order exists
    • Import products in bulk
  2. What was architecturally wrong with the old Shopify Scripts?
    • They were written in Ruby, which cannot express discounts
    • They ran in a separate sandboxed environment, adding latency and, worst of all, timing out under high traffic, exactly when checkout matters most, while being isolated from Flow, GraphQL, and Checkout UI Extensions
    • They were too fast for checkout
    • They could only run once per day
  3. Why is WebAssembly a good fit for running merchant code inside checkout?
    • Because it is the only language Shopify supports
    • Because it stores data more efficiently
    • It is sandboxed by construction (no ambient filesystem/network access), fast and predictable (sub-5ms, no cold starts, stable under load), and language-agnostic as a compilation target
    • Because it eliminates the need for an app
  4. Which is NOT one of the Function extension points described?
    • Redesigning the entire checkout page layout from scratch
    • Discount Functions
    • Cart and checkout validation
    • Delivery and payment customization
  5. How do Flow and Functions compose?
    • They cannot be used in the same store
    • Functions trigger Flow workflows directly
    • Through shared state: Flow classifies over time by writing tags (e.g. 'wholesale', 'VIP'), and a Function reads that tag at checkout to act on it
    • Flow compiles into Functions automatically

Related lessons

Business
intermediate

APIs, Webhooks, and Choosing the Right Tool

When Flow and Functions run out, you reach for the API. This lesson covers the Admin GraphQL API and its calculated-cost rate limiting, webhooks and why at-least-once delivery forces you to build for duplicates, bulk operations for large datasets, and a decision framework for choosing among all four approaches.

7 steps·~11 min
Business
intermediate

Automating a Store with Shopify Flow

Most store work is repetitive reaction: tag this order, email that customer, reorder when stock drops. Shopify Flow turns that into automation with a trigger-condition-action model. This lesson covers how Flow works, over 100 event triggers, worked workflows, and the important part, where Flow stops and you need something else.

7 steps·~11 min
Programming
beginner

When It Outgrows the Tool, and Who Owns It Meanwhile

Automations become infrastructure without anyone deciding they should. This lesson covers shadow automation and why banning it fails, documenting a flow so it survives its author, the signals that a workflow has outgrown no-code, and how to migrate without a rewrite.

8 steps·~12 min
Programming
beginner

Building Flows That Survive Contact With Reality

The concrete patterns that separate an automation that works from one that keeps working: validating input at the boundary, retrying only what is safe to retry, handling rate limits and batches, testing something you cannot easily test, and keeping secrets out of the flow.

8 steps·~12 min