Back to blog
Shopify Functions: Complete Developer Guide
E-commerce

Shopify Functions: Complete Developer Guide

Bastien AllainMarch 12, 202618 min read
shopifyfunctionsapidevelopment

Shopify Functions represent a major leap forward in how developers can customize the business logic of a Shopify store. Unlike themes or Shopify App Extensions that modify the visible interface, Functions operate directly on the backend: they let you inject custom code into Shopify's critical processes, such as discount calculations, cart validation, or delivery and payment option customization at checkout.

What Are Shopify Functions? The Future of Backend Customization

Definition: a Shopify Function is a secure piece of backend code, compiled to WebAssembly (Wasm), executed on Shopify's infrastructure. Mechanism: it receives input defined by a GraphQL query, applies business logic, and returns a JSON output -- all in under 5 ms, with no external server required.

Each Function's configuration relies on metafields, which allow merchants to adjust the function's behavior without touching code. The entire execution takes place in a sandbox environment compliant with WASI (WebAssembly System Interface), meaning no outbound network calls and no file system access -- a total security guarantee.

For developers and e-commerce agencies, Shopify Functions provide a powerful differentiation lever: they enable custom shopping experiences -- such as complex discounts like "Buy 2 products from Collection A, get 1 product from Collection B at 50% off, restricted to VIP-tagged customers" -- natively integrated into the platform.

Key Benefits of Shopify Functions

  • Performance: execution in under 5 ms, compared to 300-800 ms of typical latency for a third-party server. Every 100 ms of reduced latency at checkout can significantly improve conversion rates.
  • Security: isolated WASI sandbox with no network or file system access.
  • Native scalability: Shopify's infrastructure handles traffic spikes (Black Friday, flash sales) without any intervention.
  • Reduced operational costs: no server to host, monitor, or maintain.
  • Unified experience: customization works across online checkout, Draft Orders, and Shopify POS.
  • Distribution via the Shopify App Store: stores on any plan can benefit from Functions through public apps (custom apps remain restricted to Shopify Plus).

Beyond the API: A New Customization Philosophy

Historically, customizing Shopify store behavior required webhooks and external servers. The application would receive a notification, process the request on its own server, then send a response back to Shopify. This traditional model, while flexible, introduced significant latency and operational complexity tied to infrastructure management (hosting, monitoring, scalability).

Shopify Functions radically change this paradigm. Instead of a network round-trip, your code runs directly within Shopify's execution environment. Shopify orchestrates the call through extension targets -- predetermined hook points in the purchase journey, such as purchase.discount.run or purchase.payment-customization.run -- and executes your logic in a sandbox. The result: customization that integrates seamlessly into the native flow.

This philosophy is part of the broader Checkout Extensibility movement, where Shopify progressively opens its architecture to developers through standardized APIs (GraphQL), UI components (Shopify App Extensions), and now backend functions. The goal is clear: enable deep customization while preserving platform stability, security, and performance.

The future looks equally promising. As announced during recent Shopify Editions, Shopify continues to expand extension targets -- particularly for post-purchase scenarios and advanced customer object management. Progressive integration with the platform's AI capabilities (Shopify Magic) hints at Functions that can dynamically adapt to each shopper's behavior.

Shopify Functions vs Shopify Scripts: Strategic Migration Guide

If you have been working in the Shopify ecosystem for several years, you have likely used Shopify Scripts, the legacy customization system restricted to Shopify Plus stores. Scripts allowed modifying prices, shipping options, and payment methods through Ruby code executed in the Liquid engine. Shopify Functions are their direct successor, and Scripts is being deprecated.

CriterionShopify ScriptsShopify Functions
LanguagesRuby (proprietary environment)Rust (recommended), JavaScript/TypeScript via Javy, any Wasm-compilable language
Scope3 types (line items, shipping, payment)10+ APIs: discounts, delivery, payment, cart validation, order routing, bundles, fulfillment, pickup
ExecutionOnline checkout onlyCheckout + Draft Orders + Shopify POS
Concurrency1 active Script per typeUp to 25 simultaneous functions, independent
AccessibilityShopify Plus onlyPublic apps (all stores); custom apps (Plus)
LimitsUndocumented10 MB memory, ~256 KB Wasm binary, no outbound network calls
DeploymentBuilt-in admin editorShopify CLI + versioning + app deployment
ConfigurationRuby code to modifyMetafields + App Bridge UI (zero code for merchants)
Advanced use casesSimple discounts, basic hidingDynamic bundles, complex validation, fulfillment routing, B2B pricing

For teams still relying on Scripts, here is a structured migration approach:

  1. Audit existing Ruby Scripts: inventory each active Script, document its business logic, and identify the corresponding Function API (Discount API, Payment Customization API, etc.).
  2. Choose your language: opt for Rust if performance is critical (large carts, complex logic) or JavaScript/TypeScript (compiled via Javy) if development speed is the priority.
  3. Development and testing: use the Shopify CLI to scaffold the project, write the logic, then test locally with simulated data.
  4. Progressive deployment: publish the application via the Partner Dashboard and use metafields to activate the Function for a subset of customers or markets. Validate results in production before generalizing.
  5. Deactivate Scripts: once functional parity is confirmed, deactivate the legacy Ruby Scripts.

When Functions Are Not the Right Solution

Shopify Functions do not cover every use case:

  • Network calls to an ERP, PIM, or third-party service: Functions cannot make outbound HTTP calls. Use a webhook coupled with an external server instead.
  • Long-running asynchronous tasks: complex post-purchase processing exceeding 5 ms goes beyond execution limits. Favor an architecture based on asynchronous jobs.
  • Complex persistent state: Functions are stateless. If your logic requires maintaining state between executions, combine Functions with metafields or an external database service.

How Do Shopify Functions Work? Architecture Explained

Understanding the internal architecture of Shopify Functions is essential for using them effectively. Behind the apparent simplicity lies an optimized execution pipeline that combines GraphQL, WebAssembly, and a system of extension targets to guarantee performance and security.

The Core System: Input, Wasm Logic, and Output

Every Shopify Function follows a three-step execution schema:

  1. Input: you define a GraphQL query in an input.graphql file that specifies exactly which data your function needs -- cart products, customer tags, metafields, amounts. Shopify executes this query and passes the result as a JSON object to your function. This declarative approach ensures only necessary data is transmitted, minimizing memory footprint.

  2. Logic (processing): your business code -- compiled into a WebAssembly module -- receives this JSON object, applies calculation or validation rules, then generates a result. Code can be written in Rust (recommended by Shopify for optimal performance with large carts), JavaScript/TypeScript (compiled via Shopify's Javy tool), or any language supporting Wasm compilation.

  3. Output: the function returns a JSON object conforming to the output schema defined by the targeted API. For example, a Discount Function returns discount operations to apply; a Payment Customization Function returns operations to hide or reorder payment methods. Shopify applies these instructions instantly in the purchase flow.

The WebAssembly Advantage: Performance, Security, and Portability

The choice of WebAssembly (Wasm) as the execution format is deliberate. Wasm is a low-level binary format designed for near-native execution in a sandbox environment. Here is why Shopify adopted it:

  • Execution speed: code is pre-compiled into a compact binary. There is no interpretation or just-in-time (JIT) compilation, ensuring execution times under 5 ms -- an order of magnitude faster than a typical external API call (300-800 ms).
  • Total isolation: compliant with the WASI specification, each Function runs in a hermetic sandbox. No outbound network access, no file system access, no interaction with other functions.
  • Portability: the Wasm format is independent of the source language. Whether your team codes in Rust, JavaScript, C++, or any other compilable language, the resulting binary runs identically on Shopify's infrastructure.
  • Optimized size: Function Wasm binaries are limited to approximately 256 KB. Partners like Discount Kit reduced their function size by 40% through code optimization, directly translating to faster and more reliable checkouts.

The Role of Extension Targets in the Shopify Purchase Journey

Extension targets are the hook points where Shopify allows execution of your custom code. Each target corresponds to a specific moment in the purchase journey and a specific API.

Extension TargetCodePrimary Use Case
Discountpurchase.discount.runComplex promotions (tiers, multiple conditions, grouped purchases)
Delivery Customizationpurchase.delivery-customization.runHide, rename, or reorder delivery options
Payment Customizationpurchase.payment-customization.runHide or reorder payment gateways
Cart Validationpurchase.validation.runValidate cart contents (limits, restrictions)
Cart Transformpurchase.cart-transform.runMerge items into bundles, modify prices
Fulfillment Constraintspurchase.fulfillment-constraint.runLogistics routing rules (warehouse allocation)
Order Routing (preview)purchase.order-routing-location-rule.runCustom ranking of fulfillment locations

Each target defines available inputs (which data you can request via GraphQL) and the expected output format. Mastering this mapping is the first step in designing effective Functions.

Your End-to-End Development Workflow

Going from idea to deployment of a Shopify Function follows a structured process, powered by the Shopify CLI and the Partner Dashboard.

Prerequisites: Shopify CLI, Partner Account, and Dev Store

Before coding your first Function, make sure you have these elements in place:

  • Node.js (version 18+) and npm or pnpm installed on your machine.
  • The Shopify CLI installed globally (npm install -g @shopify/cli).
  • An active Shopify Partner account with access to the Partner Dashboard.
  • A dev store associated with your Partner account, with Checkout Extensibility enabled.

Creating Your First Function: File Structure Demystified

Function creation starts with a CLI command that generates a complete project:

shopify app generate extension --template discount_function --language rust

This command creates a directory with the following structure:

  • input.graphql: the GraphQL query defining input data.
  • src/main.rs (Rust) or src/index.js (JavaScript): the main file containing your business logic.
  • shopify.extension.toml: the configuration file declaring the extension target, API version, and Function metadata.

Here is a minimal example of an input.graphql file for a discount function:

query Input {
  cart {
    lines {
      quantity
      merchandise {
        ... on ProductVariant {
          id
          product {
            hasTags(tags: ["VIP-DISCOUNT"]) {
              hasTag: value
            }
          }
        }
      }
    }
  }
  discountNode {
    metafield(namespace: "discount-config", key: "percentage") {
      value
    }
  }
}

The input.graphql file acts as a contract between your code and Shopify. By requesting only strictly necessary data, you reduce memory footprint and speed up execution.

Essential Integration with Shopify App Extensions

A Shopify Function never operates alone. It is part of a larger Shopify application that can also include Shopify App Extensions for the user interface:

  • The Function handles backend logic (discount calculation, validation, customization).
  • The App Extension (via App Bridge and the Shopify admin) provides the configuration interface that allows merchants to create and manage Function instances without writing a single line of code.

For example, for a custom discount Function, the App Extension displays a form in the Shopify admin where the merchant can define promotion conditions (percentage, targeted collection, required customer tag). These parameters are stored in metafields and passed to the Function at each execution.

Deployment, Versioning, and Merchant Configuration

Once your Function is tested locally, deployment follows this process:

  1. Build: the Shopify CLI compiles your code into an optimized WebAssembly module.
  2. Deploy: the shopify app deploy command pushes the Wasm binary and configuration to Shopify.
  3. Publication: via the Partner Dashboard, you publish a new version of your application.
  4. Activation: the merchant installs the application, then creates a Function instance in their admin (for example, a new discount type). Configuration is done through the App Bridge interface, with no code required.

Shopify manages Function versioning: each deployment creates a new version, and the merchant can revert to a previous version at any time if issues arise.

Complete Catalog of Shopify Function APIs

Shopify currently offers over 10 Function APIs covering the entire purchase journey. Here is a structured overview of the main APIs and their practical applications.

Promotions and Discounts API

The Discount Function API (unified since April 2025, replacing the three former separate APIs: Order Discount, Product Discount, and Shipping Discount) lets you create fully custom discount types.

Practical use cases:

  • Tiered volume discounts: 10% from 100 USD, 15% from 200 USD, 20% from 300 USD -- no promo code needed.
  • Market-conditional promotions: "2 for 22 CAD" in Canada, "2 for 20 USD" in the US, with no exchange rate dependency.
  • VIP member discounts: an extra 5% automatically applied to customers tagged "VIP".
  • Shipping discounts: 25% off express shipping during the holiday season.

Delivery Customization API

The Delivery Customization API lets you sort, rename, hide, or reorder delivery options displayed at checkout.

Practical use cases:

  • Hide international shipping options for carts containing oversized or fragile items.
  • Rename delivery options to add contextual information ("Express delivery -- receive before Christmas").
  • Reorder options based on customer preferences or geographic location.

Payment Customization API

The Payment Customization API controls the display and order of payment gateways at checkout.

Practical use cases:

  • Hide "Cash on Delivery" for new customers with no purchase history.
  • Place the lowest transaction-fee payment gateway at the top position.
  • Display specific payment options based on country or cart amount.

Cart Validation and Transformation APIs

Two complementary APIs handle cart validation and transformation:

  • The Cart and Checkout Validation API blocks order completion when certain conditions are not met (quantity limits per product, age restrictions, login requirements). It also covers express payment options (Apple Pay, Google Pay).
  • The Cart Transform API merges items into dynamic bundles, modifies displayed prices, titles, or even cart line images. It is the key tool for creating sophisticated bundled offers.

Logistics and Order Routing APIs

Several APIs handle post-cart logistics:

  • Fulfillment Constraints Function API (GA): defines order allocation rules by warehouse (geofencing, split shipping limitation).
  • Local Pickup Delivery Option Generator API (GA, Shopify Plus): generates custom in-store pickup options with conditional pricing.
  • Pickup Point Delivery Option Generator API (early access, Plus): generates pickup point delivery options at third-party locations (post offices, partner stores).
  • Order Routing Location Rule API (developer preview): ranks fulfillment locations using custom logic (proximity, available stock, liquidation priority).
  • Discounts Allocator API (developer preview): controls discount allocation logic (discount caps, stacking on a single line, application to cheapest item).

Testing and Debugging Your Shopify Functions

Testing is a critical step often overlooked. A failing Function can block your customers' checkout -- reliability is non-negotiable.

Unit and Integration Tests Locally

The Shopify CLI lets you test Functions locally before any deployment:

  • Unit tests: write tests that feed your Function with simulated JSON input objects and verify the output matches expectations. In Rust, use the native test framework (#[test]). In JavaScript, use Jest or Vitest.
  • shopify app function run command: executes the Function locally with a manually provided JSON input file, simulating production behavior.
  • Integration tests: test the Function in the full application context using shopify app dev with your dev store. Verify that the configuration interface (App Extension) and backend logic (Function) work in concert.

Here is a Rust unit test example for a discount function:

#[cfg(test)]
mod tests {
    use super::*;
 
    #[test]
    fn test_discount_applied_for_vip() {
        let input = serde_json::from_str::<Input>(
            include_str!("./fixtures/vip_cart.json")
        ).unwrap();
 
        let result = function(input);
 
        assert!(result.discount_application_strategy.is_some());
        assert_eq!(result.discounts.len(), 1);
    }
 
    #[test]
    fn test_no_discount_for_regular_customer() {
        let input = serde_json::from_str::<Input>(
            include_str!("./fixtures/regular_cart.json")
        ).unwrap();
 
        let result = function(input);
 
        assert!(result.discounts.is_empty());
    }
}

Logging and Monitoring Strategies in Production

Once deployed, monitoring goes through the Partner Dashboard:

  • The dedicated Functions page displays execution metrics: invocation count, average execution time, errors, and timeouts.
  • Execution logs help identify edge cases (carts with large numbers of items, unexpected metafield configurations).
  • On error, Shopify returns a message in the Function logs. Adopt a structured logging strategy in your code (output to stderr) to facilitate diagnosis.

Best practice: set up alerts on error rate and average execution time. Gradual degradation may indicate a performance issue related to catalog growth or cart complexity.

Understanding and Respecting Technical Limits

Shopify Functions operate in a constrained environment to guarantee overall platform performance. Knowing these limits is essential to avoid production failures.

Execution, Memory, and Package Size Limits

ConstraintLimit Value
Execution timeTarget < 5 ms (hard limit is higher, but beyond 5 ms Shopify may throttle)
Memory10 MB maximum per execution
Wasm binary sizeApproximately 256 KB maximum
Wasm instructionsLimit on total instructions executed per invocation
Network callsNone -- no outbound HTTP, fetch, or websocket
File accessNone -- strict WASI sandbox
Functions per store25 functions maximum active simultaneously
StateStateless -- no persistence between executions

Best Practices to Optimize Your Wasm Code

  • Minimize GraphQL input: request only strictly necessary fields. Each additional field increases JSON input size and parsing time.
  • Choose Rust for large carts: JavaScript compiled via Javy produces heavier and slower binaries. For stores with 50+ item carts or complex logic, Rust is Shopify's recommended choice.
  • Avoid unnecessary memory allocations: in Rust, use references rather than copies. In JavaScript, avoid creating large intermediate objects.
  • Test with realistic carts: simulate the most demanding cases (maximum cart, all conditions active) to verify your Function stays within limits.
  • Optimize binary size: in Rust, enable compilation optimizations (opt-level = 'z', lto = true, strip = true). Discount Kit's experience shows that a 40% binary size reduction is achievable with these techniques.

Advanced Use Cases and Modern Architectures

Combining Functions and Metafields for Ultra-Personalized Experiences

Metafields are the preferred configuration mechanism for Shopify Functions. By defining metafields on products, customers, or the store, you can create Functions whose behavior adapts dynamically:

  • B2B pricing: a customer metafield indicates their pricing tier. The discount Function automatically applies the correct reduction percentage based on segment.
  • Geographic restrictions: a product metafield indicates authorized delivery zones. The validation Function blocks the order if the shipping address does not match.
  • Seasonal promotions: a store metafield defines promotion start and end dates. The Function checks dates at each execution, with no code redeployment needed.

Integrating Shopify Functions into a Headless Architecture

Shopify Functions are fully compatible with headless architectures using Next.js, Shopify Hydrogen, or any other front-end framework connected to the Storefront API. In a headless architecture:

  • The front-end (Next.js, Hydrogen) handles display and user experience.
  • The Storefront API (GraphQL) manages cart and checkout interactions.
  • Shopify Functions execute automatically on the backend during checkout calculation -- whether the front-end is a classic Liquid theme or a headless application.

The major advantage: your custom business rules (discounts, validation, customization) work identically regardless of the sales channel. A Function deployed once applies to online checkout, POS, Draft Orders, and any headless application connected via the Storefront API.

For agencies deploying headless architectures on Shopify, Functions eliminate the need to reimplement business logic on the front-end. Customization remains centralized on the platform, simplifying maintenance and guaranteeing consistency across channels.

FAQ: Common Questions About Shopify Functions

What Is the Difference Between Shopify Functions and Legacy Shopify Scripts?

Shopify Scripts is a legacy customization system based on Ruby, restricted to Shopify Plus, and being deprecated. Shopify Functions are their successor: they support Rust and JavaScript, cover 10+ APIs (vs. 3 for Scripts), work across all channels (checkout, POS, Draft Orders), and allow 25 simultaneously active functions. Migration is strategically essential before Scripts reach end of support.

How Do You Test and Debug a Shopify Function Before Deployment?

Use the shopify app function run command from the Shopify CLI to execute your Function locally with a simulated JSON input file. Write unit tests (native test framework in Rust, Jest or Vitest in JavaScript) and integration tests via shopify app dev with a dev store. In production, monitor metrics through the Partner Dashboard.

What Are the Performance Limits of a Shopify Function?

The main limits are: 10 MB of memory per execution, approximately 256 KB for the compiled Wasm binary, a target execution time under 5 ms, and no outbound network calls. A store can run up to 25 functions simultaneously. Functions are stateless: no state is preserved between executions.

Can You Use Shopify Functions with a Headless Store?

Yes. Shopify Functions execute on the backend, independently of the front-end. Whether you use Next.js, Shopify Hydrogen, or any other headless framework connected to the Storefront API, Functions apply automatically during checkout calculation. Your custom business rules work identically across all sales channels.

Related posts