
What Are Shopify Functions?
Shopify Functions is a backend extensibility framework that lets developers write custom logic that executes inside Shopify’s checkout and order management pipeline. Think of a Function as a small, purpose-built program that Shopify calls at a specific moment during checkout, passes a set of data to, and expects a specific type of response from.
Before Functions existed, customizing Shopify’s backend behavior meant either accepting the platform’s built-in rules or working around them with workarounds that were fragile, slow, or both. Shopify Scripts offered some customization for Plus merchants but came with significant limitations in data access, execution speed, and language flexibility.
Functions change this model entirely. They are written in JavaScript, TypeScript, Rust, or any language that compiles to WebAssembly. They receive their input data through a typed GraphQL query that you define, which means you specify exactly which data your function needs and Shopify provides precisely that. They return a structured output that Shopify applies to the checkout or order in real time.
The result is a customization system that is fast enough to run in Shopify’s critical checkout path, expressive enough to handle complex business logic, and integrated deeply enough with Shopify’s data model to make decisions based on rich cart, customer, product, and metafield information.
How Shopify Functions Work Under the Hood
Understanding the execution model behind Functions makes it much easier to write effective ones. The process follows a consistent pattern regardless of which Function API type you are using.
The Input Query Model
Every Function begins with a run.graphql file that defines a GraphQL query. This query is your function’s declaration of what data it needs from Shopify at execution time. When Shopify calls your Function during a checkout, it runs this query against its internal data model and delivers the result to your function as a typed JSON object.
This design is deliberate. Instead of giving every function access to the entire cart and customer record regardless of what it needs, Shopify executes only the query you have defined. Your Function receives exactly the data it requested and nothing more. This keeps execution fast and reduces the surface area for unintended data access.
The Function Execution Cycle
Here is the sequence of events every time a Shopify Function runs:
- A customer action triggers the Function. For a Discount Function this might be adding a product to cart, applying a discount code, or proceeding to checkout.
- Shopify executes the run.graphql query defined in your Function and assembles the typed input object.
- Shopify passes this input object to your compiled WebAssembly binary.
- Your Function logic processes the input and returns a structured output: a list of discount operations, payment customizations, delivery changes, or cart transformations depending on the Function type.
- Shopify applies the output to the checkout or order in real time and continues the checkout flow.
The entire cycle from trigger to applied output takes microseconds. Functions do not make network calls, do not read from disk, and do not allocate unbounded memory. They are deterministic, sandboxed, and fast by design.
WebAssembly as the Runtime
WebAssembly is the reason Functions can run directly in Shopify’s infrastructure rather than in an isolated external sandbox. Wasm binaries are compact, safe to execute in a multi-tenant environment, and platform-independent. Shopify compiles your TypeScript or JavaScript to Wasm during the deployment process using Javy, a JavaScript-to-Wasm compiler maintained by Shopify.
You do not need to understand WebAssembly to write Functions. The Shopify CLI handles the compilation step automatically when you run the build or deploy commands. What matters for your day-to-day development is writing correct TypeScript logic and a well-formed GraphQL input query.
Metafield Configuration for Merchant Customization
Many Functions need to be configurable by the merchant without requiring a code change and redeployment. Shopify Functions support this through metafields. You can expose configuration values as metafields on the Function’s app installation, and merchants can edit those values directly from the Shopify admin.
For example, a discount threshold Function might expose the minimum cart value as a configurable metafield. The merchant sets the threshold to $75 in the admin. Your Function reads that metafield value from its input query and applies the discount only when the cart reaches that threshold. No code changes required for the merchant to adjust the value.
The Complete Map of Shopify Function API Types
Shopify provides several distinct Function API types, each targeting a specific part of the checkout or order pipeline. Understanding which API type handles which scenario is essential before you start building.
Discount Functions (Product, Order, and Shipping)
The Discount Function API handles all custom discount logic. It replaces the line item and shipping script functionality from Shopify Scripts and extends well beyond what Scripts could do. There are three distinct targets within the Discount API:
- Product Discounts: Apply price reductions to specific line items based on product conditions, customer segments, quantities, or any combination of cart data.
- Order Discounts: Apply a discount to the entire order total. Used for spend-based promotions, loyalty rewards, and B2B pricing adjustments that apply at the cart level.
- Shipping Discounts: Apply discounts to shipping rates. Used for free shipping thresholds, carrier-specific discounts, and loyalty-based shipping perks.
Payment Customization Function
The Payment Customization Function controls which payment methods are shown, hidden, or reordered at checkout. It runs after the customer reaches the payment step and can make decisions based on cart contents, cart total, customer tags, shipping destination, or any other data in its input query.
Delivery Customization Function
The Delivery Customization Function controls the shipping options displayed to the customer during checkout. It can hide irrelevant shipping methods, rename methods to match your brand’s language, reorder methods to promote a preferred option, or apply surcharges to specific rates. It does not calculate new shipping rates from scratch but transforms the rates that Shopify’s shipping system has already generated.
Cart Transform Function
The Cart Transform Function modifies the structure of the cart during checkout. It can expand a single product line item into multiple component items, merge multiple items into a single bundled line item, or add items to the cart programmatically. This is the Function type used for native bundle implementation, kit customization, and free-gift-with-purchase logic.
Fulfillment Constraints Function
The Fulfillment Constraints Function defines custom rules for how Shopify assigns order line items to fulfillment locations. It allows complex multi-location routing logic: routing specific product categories to specific warehouses, enforcing geographic assignment rules, or preventing split shipments for orders that must ship complete.
Order Routing Location Rule Function
The Order Routing Location Rule Function determines which fulfillment location handles a given order when multiple locations are eligible. It runs Shopify’s location selection process through custom logic rather than Shopify’s default proximity or priority-based selection.
Pickup Point Delivery Option Generator
This Function type generates custom pickup point options for the customer during checkout. It is used by merchants who operate their own pickup locations or partner networks that are not natively integrated with Shopify’s carrier-calculated pickup options.
Real Use Case 1: Tiered Volume Discounts for Wholesale Buyers
A wholesale distributor sells products to retailers at tiered pricing: 5 percent off for orders of 10 or more units of any single product, 10 percent off for 25 or more units, and 15 percent off for 50 or more units. The discount applies per line item based on the quantity of that specific product, not the overall cart total.
This is a Product Discount Function. The function reads each line item’s quantity and applies the correct discount tier independently to each one.
Input Query (run.graphql)
query RunInput {
cart {
lines {
id
quantity
}
}
}
Function Logic (run.ts)
import type { RunInput, FunctionRunResult } from ‘../generated/api’;
const TIERS = [
{ minQty: 50, pct: 15 },
{ minQty: 25, pct: 10 },
{ minQty: 10, pct: 5 },
];
export function run(input: RunInput): FunctionRunResult {
const discounts = input.cart.lines
.flatMap(line => {
const tier = TIERS.find(t => line.quantity >= t.minQty);
if (!tier) return [];
return [{
targets: [{ cartLine: { id: line.id } }],
value: { percentage: { value: tier.pct.toString() } },
message: `${tier.pct}% volume discount`,
}];
});
return { discounts, discountApplicationStrategy: ‘FIRST’ };
}
The TIERS array is ordered from highest to lowest so the first match always returns the best available discount. Any line item with a quantity below 10 produces an empty array and receives no discount. The result is a clean, data-driven volume pricing engine that handles any number of tiers without additional code.
Real Use Case 2: B2B Customer-Specific Pricing
An industrial supplier has a mix of retail and B2B customers. B2B customers are identified by a customer tag called b2b-tier-1 or b2b-tier-2, each with a different blanket discount applied to all products in their cart. Retail customers receive no discount.
This is an Order Discount Function because the discount applies to the full cart rather than individual products.
Input Query (run.graphql)
query RunInput {
cart {
buyerIdentity {
customer {
tags
}
}
cost {
subtotalAmount { amount }
}
}
}
Function Logic (run.ts)
import type { RunInput, FunctionRunResult } from ‘../generated/api’;
const B2B_TIERS: Record<string, number> = {
‘b2b-tier-1’: 10,
‘b2b-tier-2’: 18,
};
const EMPTY: FunctionRunResult = {
discounts: [],
discountApplicationStrategy: ‘FIRST’,
};
export function run(input: RunInput): FunctionRunResult {
const tags = input.cart.buyerIdentity?.customer?.tags ?? [];
const matchedTag = Object.keys(B2B_TIERS)
.find(tag => tags.includes(tag));
if (!matchedTag) return EMPTY;
const pct = B2B_TIERS[matchedTag];
return {
discounts: [{
targets: [{ orderSubtotal: { excludedVariantIds: [] } }],
value: { percentage: { value: pct.toString() } },
message: `B2B pricing: ${pct}% off`,
}],
discountApplicationStrategy: ‘FIRST’,
};
}
The B2B_TIERS object makes the tier configuration easy to extend without touching the core logic. Adding a new tier is a single line change. Guest customers and untagged customers fall through to the EMPTY result with no discount applied.
Real Use Case 3: Free Shipping Threshold with Carrier Exclusions
A fashion retailer offers free standard shipping on all orders over $100. However, the promotion excludes express and overnight shipping options, which should always be charged at full price. Customers below $100 see standard rates as normal.
This is a Shipping Discount Function. It reads the cart total and the names of the available shipping rates, then applies a 100 percent discount to standard rates when the threshold is met.
Input Query (run.graphql)
query RunInput {
cart {
cost {
subtotalAmount { amount }
}
deliveryGroups {
deliveryOptions {
handle
title
cost { amount }
}
}
}
}
Function Logic (run.ts)
import type { RunInput, FunctionRunResult } from ‘../generated/api’;
const FREE_SHIPPING_THRESHOLD = 100.0;
const EXCLUDED_KEYWORDS = [‘express’, ‘overnight’, ‘priority’];
export function run(input: RunInput): FunctionRunResult {
const subtotal = parseFloat(
input.cart.cost.subtotalAmount.amount
);
if (subtotal < FREE_SHIPPING_THRESHOLD) {
return { discounts: [], discountApplicationStrategy: ‘FIRST’ };
}
const targets = input.cart.deliveryGroups
.flatMap(g => g.deliveryOptions)
.filter(opt => {
const titleLower = opt.title.toLowerCase();
return !EXCLUDED_KEYWORDS.some(kw => titleLower.includes(kw));
})
.map(opt => ({ deliveryOption: { handle: opt.handle } }));
if (targets.length === 0) {
return { discounts: [], discountApplicationStrategy: ‘FIRST’ };
}
return {
discounts: [{
targets,
value: { percentage: { value: ‘100’ } },
message: ‘Free standard shipping on orders over $100’,
}],
discountApplicationStrategy: ‘FIRST’,
};
}
The EXCLUDED_KEYWORDS array makes the exclusion list easy to update without touching the logic. A title case-insensitive check ensures the exclusion works regardless of how the carrier names their services in Shopify’s shipping configuration.
Real Use Case 4: Hiding Payment Methods Based on Cart Value and Destination
A business-to-consumer retailer offers an invoice payment option called Pay by Invoice but only wants to make it available for orders above $500. For international orders, the option should also be hidden regardless of cart value because the invoicing system only supports domestic billing addresses.
This is a Payment Customization Function. It evaluates two conditions independently and hides the payment method if either condition is met.
Input Query (run.graphql)
query RunInput {
cart {
cost {
subtotalAmount { amount }
}
deliveryGroups {
deliveryAddress {
countryCode
}
}
}
paymentMethods {
id
name
}
}
Function Logic (run.ts)
import type { RunInput, FunctionRunResult } from ‘../generated/api’;
const MIN_AMOUNT = 500.0;
const ALLOWED_COUNTRY = ‘US’;
const METHOD_NAME = ‘Pay by Invoice’;
export function run(input: RunInput): FunctionRunResult {
const subtotal = parseFloat(
input.cart.cost.subtotalAmount.amount
);
const countryCode = input.cart.deliveryGroups
.find(g => g.deliveryAddress?.countryCode)
?.deliveryAddress?.countryCode ?? null;
const shouldHide =
subtotal < MIN_AMOUNT || countryCode !== ALLOWED_COUNTRY;
if (!shouldHide) return { operations: [] };
const method = input.paymentMethods.find(
m => m.name === METHOD_NAME
);
if (!method) return { operations: [] };
return {
operations: [{ hide: { paymentMethodId: method.id } }]
};
}
The constants at the top of the file make the business rules visible at a glance. The shouldHide boolean cleanly combines both conditions into a single readable expression. If the payment method does not exist in the current session, the function exits safely without error.
Real Use Case 5: Renaming and Reordering Shipping Options
A home goods retailer uses a third-party carrier service that returns shipping rate names in a generic technical format like STANDARD_GROUND and EXPRESS_2DAY. The marketing team wants customers to see branded names like Free Standard Delivery (3 to 5 days) and Fast Track Delivery (2 days). They also want the branded names sorted with the cheapest option first.
This is a Delivery Customization Function. It reads the delivery option handles and titles and returns rename and reorder operations.
Input Query (run.graphql)
query RunInput {
cart {
deliveryGroups {
id
deliveryOptions {
handle
title
cost { amount }
}
}
}
}
Function Logic (run.ts)
import type { RunInput, FunctionRunResult } from ‘../generated/api’;
const RENAME_MAP: Record<string, string> = {
‘STANDARD_GROUND’: ‘Free Standard Delivery (3-5 days)’,
‘EXPRESS_2DAY’: ‘Fast Track Delivery (2 days)’,
};
export function run(input: RunInput): FunctionRunResult {
const operations = input.cart.deliveryGroups.flatMap(group => {
const sorted = […group.deliveryOptions].sort(
(a, b) => parseFloat(a.cost.amount) – parseFloat(b.cost.amount)
);
return sorted.flatMap((opt, index) => {
const ops = [];
const newTitle = RENAME_MAP[opt.title];
if (newTitle) {
ops.push({ rename: { deliveryOptionHandle: opt.handle,
title: newTitle } });
}
ops.push({ move: { deliveryOptionHandle: opt.handle,
index } });
return ops;
});
});
return { operations };
}
The RENAME_MAP keeps the business configuration separate from the logic. Updating a display name requires editing one line in the map, not hunting through conditional statements. The sort by cost ensures the cheapest option always appears first regardless of the order the carrier returns the rates.
Real Use Case 6: Cart Transform for Native Bundle Products
A software accessories retailer sells a Work From Home Bundle as a single product in their Shopify catalog. When a customer adds this bundle to their cart, it should automatically expand in the cart to show the individual component items: a webcam, a headset, and a laptop stand. Each component should display as a separate line item with its own price breakdown so the customer understands exactly what they are getting.
This is a Cart Transform Function. It detects the bundle product by its tag and expands it into component line items.
Input Query (run.graphql)
query RunInput {
cart {
lines {
id
quantity
merchandise {
… on ProductVariant {
id
product {
tags
metafield(namespace: “bundle”, key: “components”) {
value
}
}
}
}
}
}
}
Function Logic (run.ts)
import type { RunInput, FunctionRunResult } from ‘../generated/api’;
const BUNDLE_TAG = ‘bundle’;
export function run(input: RunInput): FunctionRunResult {
const operations = input.cart.lines.flatMap(line => {
const variant = line.merchandise;
if (variant.__typename !== ‘ProductVariant’) return [];
if (!variant.product.tags.includes(BUNDLE_TAG)) return [];
const raw = variant.product
.metafield?.value;
if (!raw) return [];
const components: Array<{
variantId: string;
quantity: number;
}> = JSON.parse(raw);
return [{
expand: {
cartLineId: line.id,
expandedCartItems: components.map(c => ({
merchandiseId: c.variantId,
quantity: c.quantity * line.quantity,
})),
}
}];
});
return { operations };
}
The bundle’s component data is stored in a Shopify metafield as a JSON array of variant IDs and quantities. This keeps the bundle definition in Shopify’s data model rather than hardcoded in the Function, allowing merchants to update bundle contents through the Shopify admin without touching any code.
Real Use Case 7: Smart Fulfillment Routing by Product Type
A large retailer operates three fulfillment centers: one for electronics, one for apparel, and one general warehouse. When an order contains only electronics, it should route to the electronics warehouse. When it contains only apparel, it should route to the apparel warehouse. Mixed orders route to the general warehouse.
This is a Fulfillment Constraints Function. It reads the product tags for each line item and assigns fulfillment location constraints based on the mix of products in the order.
Input Query (run.graphql)
query RunInput {
cart {
lines {
id
merchandise {
… on ProductVariant {
product { tags }
}
}
}
}
locations {
id
name
}
}
Function Logic (run.ts)
import type { RunInput, FunctionRunResult } from ‘../generated/api’;
const LOCATION_MAP: Record<string, string> = {
‘electronics’: ‘Electronics Warehouse’,
‘apparel’: ‘Apparel Warehouse’,
};
const GENERAL = ‘General Warehouse’;
export function run(input: RunInput): FunctionRunResult {
const productTypes = new Set(
input.cart.lines.flatMap(line => {
const v = line.merchandise;
if (v.__typename !== ‘ProductVariant’) return [];
return Object.keys(LOCATION_MAP)
.filter(key => v.product.tags.includes(key));
})
);
const targetName = productTypes.size === 1
? LOCATION_MAP[[…productTypes][0]]
: GENERAL;
const location = input.locations.find(l => l.name === targetName);
if (!location) return { operations: [] };
return {
operations: [{
addConstraint: {
locationId: location.id,
deliveryGroup: { index: 0 }
}
}]
};
}
A Set is used to collect the distinct product types present in the cart. If the Set has exactly one entry, the order is single-type and routes to the specialist warehouse. Any other result routes to the general warehouse. The LOCATION_MAP keeps warehouse assignments configurable without touching the routing logic.
Using Metafields for Merchant-Configurable Functions
Hardcoding business rules like discount thresholds and eligible customer tags directly in your Function source code means that every business rule change requires a code edit and a redeployment. For merchant-facing apps and stores where non-developers need to adjust promotional rules regularly, this is impractical.
Shopify Functions solve this through metafield configuration. You declare configuration fields in your shopify.extension.toml file, and Shopify exposes them as an editable interface in the Shopify admin. Your Function reads the configured values through its input query at execution time.
Declaring Configuration in shopify.extension.toml
[[extensions]]
type = “function”
name = “Volume Discount”
handle = “volume-discount”
[[extensions.metafields]]
namespace = “volume-discount”
key = “tier1_min_qty”
[[extensions.metafields]]
namespace = “volume-discount”
key = “tier1_percentage”
Reading Configuration in the Input Query
query RunInput {
discountNode {
metafield(namespace: “volume-discount”, key: “tier1_min_qty”) {
value
}
}
cart {
lines { id quantity }
}
}
The merchant sets the tier thresholds from the Shopify admin without any developer involvement. The Function reads the current values on every execution. Promotional rules can be updated instantly by a marketing manager without a deployment cycle.
Testing and Debugging Shopify Functions
Functions run in a sandboxed WebAssembly environment with no console output and no network access. Standard JavaScript debugging tools do not apply. The Shopify CLI provides dedicated tools for testing and validating Functions before they are deployed to a live store.
Local Unit Testing with the CLI
The shopify app function run command executes your Function locally against a JSON input payload you provide. This is the fastest way to verify your logic covers all your test scenarios without touching a store.
shopify app function run –export run –input test-payload.json
Build a library of payload files covering every scenario your Function handles: minimum qualifying cart, maximum qualifying cart, edge cases at threshold boundaries, customers with and without relevant tags, empty carts, and any scenario specific to your business logic. Running all payloads takes seconds and gives you high confidence before deployment.
GraphQL Input Query Validation
The Shopify CLI validates your run.graphql query against Shopify’s schema during the build step. If your query requests a field that does not exist or uses incorrect argument syntax, the build will fail with a descriptive error. This catches data access mistakes before they reach a live store.
# Build and validate locally
shopify app build
End-to-End Testing on a Development Store
After local unit testing, deploy your Function to a development store and test the full checkout flow. Place test orders that cover every scenario in your payload library. Use Shopify’s test payment gateway to place orders without incurring charges.
Check the order details in the Shopify admin after each test order to confirm the discount, payment customization, or delivery change was applied correctly. The discount line on the order confirmation is the ground truth for whether your Function executed as intended.
Monitoring in Production
Once your Function is live, monitor your store’s checkout conversion rate for any unexpected changes. A sudden drop in conversion rate after deploying a Function often indicates a logic error that is hiding payment methods or applying discounts incorrectly for a subset of customers.
Shopify’s admin provides error logs for Function execution failures. Check these logs regularly after a new deployment and set up an alert through your analytics platform if checkout completion rate drops below your baseline.
Shopify Functions vs Third-Party Apps: When to Build vs When to Buy
Not every checkout customization requirement needs a custom Function. Shopify’s app ecosystem includes thousands of apps that provide discount logic, payment customization, shipping tools, and bundle management out of the box. Understanding when to write a custom Function and when to use an existing app saves significant development time.
Build a Custom Function When
- The business logic is unique to your store and cannot be configured in any existing app
- You need sub-millisecond execution speed and the existing apps introduce noticeable checkout latency
- The customization involves sensitive data and you need full control over what data is accessed
- You need the configuration to be tightly integrated with your store’s existing metafield data model
- You are building a Shopify app to distribute to multiple merchants and need a reusable Function
Use an Existing App When
- Your requirement is a standard use case that existing apps handle well, such as buy-one-get-one discounts or tiered free shipping thresholds
- The development cost of a custom Function exceeds the cost of an app subscription for your expected usage period
- You need the customization live quickly and do not have time for a full development and testing cycle
- The app provides merchant-facing configuration UI that would take significant additional development to replicate
For guidance on selecting the right Shopify and WordPress plugins and tools for your store, our guide on Best WordPress Plugins for Business Websites in 2026 covers evaluation criteria that apply equally well to Shopify app selection.
If you need help designing, building, or auditing Shopify Functions for your store, our Shopify development service covers custom Function development, testing, and deployment for stores of all sizes.
Frequently Asked Questions
Q1. What programming languages can I use to write Shopify Functions?
Shopify Functions compile to WebAssembly, so any language that compiles to Wasm is technically supported. In practice, JavaScript and TypeScript are the most widely used because the Shopify CLI generates TypeScript scaffolding by default and the ecosystem is most mature. Rust is a strong alternative when binary size and execution speed are priorities. AssemblyScript, a TypeScript-like language that compiles directly to Wasm, is also supported.
Q2. Do Shopify Functions work on all Shopify plans?
Yes. Shopify Functions are available on all plans including Basic, Shopify, Advanced, and Plus. This is a significant improvement over Shopify Scripts, which were exclusively available on Shopify Plus. Some specific Function API types may have plan-level restrictions, so always check the Shopify developer documentation for the specific API you are targeting.
Q3. Can a Shopify Function access external APIs or databases?
No. Shopify Functions run in a WebAssembly sandbox with no network access. They cannot make HTTP requests, connect to databases, or read from the file system. If your logic requires data from an external source, the recommended pattern is to pre-populate that data into Shopify metafields using a background process or webhook handler, then read the metafield values through the Function’s input query.
Q4. How do multiple Functions interact when more than one is active on a store?
Multiple Functions of the same type can be active simultaneously. The discountApplicationStrategy setting on each Discount Function controls how discounts from different Functions combine. The FIRST strategy applies the first qualifying discount and ignores others. The ALL strategy applies all qualifying discounts. For Payment Customization and Delivery Customization Functions, multiple active Functions run in sequence and their operations are combined.
Q5. How long does it take for a deployed Function to become active on a store?
Function deployment via the Shopify CLI typically takes between 30 seconds and a few minutes to propagate. After you activate the Function in the Shopify admin, it begins executing on the next checkout session. There is no need for a store restart or cache flush. Changes to Function logic require a new deployment and version update through the CLI.
Q6. Can I use Shopify Functions to add items to the cart programmatically?
Yes, but only through the Cart Transform Function type, not through the Discount or Payment Customization Function types. The Cart Transform Function can expand a line item into multiple items or add new items during checkout. It is the correct tool for free-gift-with-purchase logic, bundle expansion, and any scenario that requires modifying the cart’s line item structure rather than just its pricing.
Conclusion
Shopify Functions represent a genuinely new level of checkout extensibility for Shopify merchants and developers. The combination of WebAssembly execution speed, a rich GraphQL data input model, metafield-driven merchant configuration, and availability across all plans makes Functions the most powerful customization tool Shopify has ever offered at the checkout layer.
The real-world use cases in this guide cover the most common scenarios: volume discounts, B2B pricing tiers, free shipping thresholds, payment method customization, shipping option branding, native bundle expansion, and smart fulfillment routing. Each of these patterns is a starting point you can adapt to your store’s specific business rules.