- Shopify Functions
- Shopify Functions is a serverless execution environment that lets developers deploy custom business logic — discounts, cart transforms, delivery rules, payment filters — that runs server-side inside Shopify's checkout infrastructure as a WebAssembly module, with deterministic execution under 5ms and zero client-side JavaScript.
- Also known as: Functions API, Shopify Functions API
How Shopify Functions work
Every Shopify Function is a WebAssembly (WASM) module that Shopify executes within its own infrastructure at specific points in the commerce lifecycle. When a buyer proceeds to checkout, adds a product to their cart, or selects a shipping method, Shopify invokes any Functions registered for that event type on the store. The Function receives a structured JSON input — defined by a GraphQL query fragment you write — and must return a structured JSON output within tight resource constraints: under 5ms of CPU time, under 10MB of memory. There are no network calls, no file I/O, no asynchronous operations. The Function is a pure, deterministic transform: same input always produces the same output, which is what makes it safe to invoke on every checkout event without unpredictable side effects.
The input and output schemas are defined in GraphQL. For each Function type (product discounts, order discounts, cart transform, delivery customization, payment customization, fulfillment constraints), Shopify publishes a fixed output schema — the shape of the response your Function must return. The input is flexible within a defined universe of available fields: you write a GraphQL query fragment in your extension's input.graphql file specifying exactly which cart, buyer, product, and discount fields your Function needs. Shopify evaluates your query at invocation time and passes the result as the Function's input. This design means you only receive — and pay the compute cost for — the data your logic actually requires. Requesting 50 fields when you only need 5 adds latency for no benefit.
Functions can be written in Rust (the native language, compiled directly to WASM, fastest execution) or TypeScript/JavaScript via the @shopify/shopify-function package, which uses the Javy compiler (a JS-to-WASM transpiler built on QuickJS) to compile TypeScript to WASM. TypeScript is significantly easier to work with for most web development teams — you get type safety from Shopify's generated types, a familiar language, and fast iteration cycles. Rust is worth the additional complexity for Functions that perform heavy mathematical logic, process very large carts with many line items, or need to achieve the lowest possible execution latency.
Functions are deployed as extensions within a Shopify app, using Shopify CLI. Running shopify app deploy compiles your TypeScript or Rust source to WASM locally and uploads it to Shopify's infrastructure. The app can be a public Shopify app (available on the App Store) or a custom app installed on a single store. Once deployed, you create a discount or customization record in Shopify Admin that references the Function by its ID — the Function is activated through that record, not directly.
When to use Functions
The most common Function type is product discounts — tiered volume pricing (buy 10+, save 10%; buy 25+, save 15%), bundle discounts, loyalty-tier pricing, B2B account pricing, promotional offer logic. Before Functions, all of these required either Shopify Scripts (Plus-only, now deprecated), a third-party app running client-side JavaScript, or manual draft order manipulation. Functions provide a server-side, officially supported execution path that works with Shopify's discount stacking logic, is surfaced in the checkout order summary, and is guaranteed to remain compatible with future Shopify checkout updates.
Cart transform Functions go further than discounts: they can add, remove, or merge cart line items before checkout renders. This is the right tool for implementing bundle mechanics (add 3 of product A, automatically add 1 of product B as a free gift), subscription upgrade upsells that inject a different SKU, or automated case-pack consolidation in wholesale contexts where 12 individual units should become one case-pack line item. Cart transforms run before the buyer sees the cart, so there is no client-side flicker or layout shift.
Delivery customization Functions let you conditionally hide, rename, reorder, or add price surcharges to shipping options at checkout. Practical use cases include: hiding express shipping for orders containing oversized items, showing custom rate names that match your warehouse's carrier contracts rather than generic "Express" labels, suppressing certain shipping methods for high-risk delivery addresses, or adding a handling surcharge for rural delivery postcodes. This type of checkout customization was previously impossible without checkout.liquid, which is deprecated.
Payment customization Functions allow conditional visibility and ordering of payment methods. You can hide invoice payment methods for non-B2B buyers (preventing retail customers from seeing a "Pay by invoice" option), suppress Buy Now Pay Later options for orders below a minimum value where BNPL fees erode margin, or reorder payment methods to surface your preferred option for a given market. These conditions are evaluated server-side per buyer context, not via client-side DOM manipulation.
Fulfillment constraints Functions are a newer Function type that allow merchants to restrict which fulfilment service or warehouse handles a given order or order line. If certain products can only ship from a specific warehouse, or an order must be fulfilled from the location closest to the shipping address, constraints Functions encode that logic server-side in a way that integrates directly with Shopify's fulfilment routing.
Functions vs. Scripts (legacy)
Shopify Scripts was the predecessor to Functions, available only on Shopify Plus. Scripts ran Ruby code server-side at checkout for discount and shipping logic. They were functional but limited in important ways: Ruby execution in Shopify's sandboxed VM was slow (50-100ms per invocation), the Ruby Script API was proprietary and not aligned with Shopify's public Admin or Storefront APIs, and Scripts could only be deployed by the merchant themselves or their development partner — not by third-party app developers building installable extensions. This meant the ecosystem of Scripts-powered apps was essentially nonexistent.
Functions replace Scripts in every meaningful dimension. The WASM runtime executes 10-50x faster than the Ruby Scripts VM. The input and output schemas are based on standard GraphQL types that align with Shopify's public API surface. Functions are deployable by third-party app developers as extensions, enabling a rich ecosystem of installable Functions-powered apps (TOPCODE Upsell 2.0 is one example). And critically, Functions work on all Shopify plans for third-party apps — though deploying your own custom Functions as a merchant still requires Plus.
Shopify announced Scripts deprecation in 2023 with sunset dates that affect existing Plus stores. If you are still running Scripts-based discount or cart transform logic, migration to Functions is not optional — the deprecation timeline is real. The migration is a rewrite: Scripts used a proprietary Ruby DSL; Functions use TypeScript or Rust with a standard GraphQL schema. You cannot port a Script to a Function mechanically. Budget a full development sprint per Script, plus QA time to validate that the new Function produces equivalent results across your test cases.
Limitations
The constraints that make Functions fast are also the constraints that define where they cannot be used. Functions cannot make HTTP calls. This is a hard constraint, not a configuration option. If your discount logic depends on an external loyalty point balance API or a real-time inventory availability check, the Function cannot call that service. The workaround is to pre-compute the necessary data and store it in a cart metafield or discount metafield before checkout is reached — the Function reads the pre-computed cached value rather than calling the external service in real time. Shopify Flow or a webhook handler is typically used to write the pre-computed data into metafields.
Functions are stateless. There is no persistent state between Function invocations. If you need to track cumulative purchase history (e.g., "this buyer has spent more than $1,000 this calendar year, unlock a reward tier"), that state must live in a metafield on the customer or cart, pre-populated by a separate mechanism — typically a Shopify Flow automation triggered by the orders/paid webhook that updates a lifetime spend metafield on the customer record.
Input data is scoped to checkout context. Functions can access cart lines, buyer identity, discount node metafields, and some product data — but not your full order history, current fulfilment status of other orders, or external system data. Any data your logic requires that is not in the Function input schema needs to be pre-loaded into accessible metafields. This drives an architectural pattern where your pre-computation layer (webhooks, Flow, scheduled jobs) is responsible for keeping metafields fresh, and Functions are pure consumers of that pre-computed state.
CPU time limit is 5ms. For most business logic this is generous — a typical discount Function runs in 0.5-2ms. However, Functions that need to iterate over very large datasets (e.g., evaluating a cart against a product exclusion list with tens of thousands of SKUs) can approach the limit. The solution is to pre-process large datasets into data structures (hash sets, bloom filters) that allow O(1) lookup at Function evaluation time rather than O(n) iteration.
Frequently asked questions
- Do I need Shopify Plus to use Functions?
Not for all Function types. Cart transform, delivery customization, and payment customization Functions are available on all plans when installed via a third-party app — any Shopify merchant can install TOPCODE Upsell 2.0 and benefit from its Functions-powered offer evaluation. However, deploying your own custom Functions (writing and deploying Function code yourself as a merchant) requires Shopify Plus or a Partner development store. Check the Shopify Functions documentation at shopify.dev/docs/api/functions for current plan requirements per Function type.
- How do I test a Function locally before deploying to production?
Shopify CLI provides two levels of testing. For unit testing, use `shopify app function run --input input.json` to pass a JSON file matching your Function's input schema and see the output without any network calls. This is fast (sub-second) and works completely offline. For full end-to-end testing, deploy to a Partner development store with `shopify app deploy`, create a discount referencing your Function, and test through a live checkout session. The CLI supports `--log` flag streaming to observe real-time Function execution including input, output, and execution time during checkout testing.
- Can one store have multiple Functions of the same type active simultaneously?
Yes. A store can have multiple active discount Functions — each registered as a separate discount record. Shopify evaluates all active discounts at checkout and applies them according to the discount application strategy set on each discount (FIRST, ALL, or MAXIMUM). Multiple delivery customization Functions are also supported, applied sequentially. The order of evaluation between multiple Functions of the same type is not guaranteed, so design Functions to be independent of each other's output. If evaluation order matters for correctness, consolidate the logic into a single Function.
