ADSX
FEBRUARY 21, 2026 // UPDATED FEB 21, 2026

Shopify Functions: Extend Store Logic with Custom Code

Learn how to build custom Shopify Functions to extend store logic without modifying core Shopify code. Discover use cases, development workflows, WebAssembly performance benefits, and best practices for implementing custom discount, shipping, and payment logic.

Shopify Functions represent a fundamental shift in how developers extend store logic without forking Shopify's core codebase. Unlike traditional apps that modify behavior through admin UIs and external APIs, Functions execute lightweight code at critical commerce moments—immediately, reliably, and at scale.

For Shopify merchants and developers building sophisticated storefronts, Functions are the answer to questions like: "How do I implement custom discount logic without third-party dependencies?" "Can I dynamically calculate shipping rates based on our proprietary algorithm?" "What if I need to customize payment method availability by location?"

This comprehensive guide covers everything you need to know about building, deploying, and scaling Shopify Functions—from foundational concepts to production best practices.

Shopify Functions enabling custom commerce logic at the edge
SHOPIFY FUNCTIONS ENABLING CUSTOM COMMERCE LOGIC AT THE EDGE

What Are Shopify Functions?

Shopify Functions are lightweight, serverless code snippets that execute at critical moments in the Shopify commerce lifecycle. They allow developers to customize store behavior—calculating discounts, determining shipping options, validating payments—without modifying Shopify's core infrastructure.

Key Characteristics

Serverless Architecture Functions run in Shopify's managed infrastructure. You write code, deploy it, and Shopify handles scaling, reliability, and performance optimization. No servers to manage, no infrastructure costs.

WebAssembly Execution Functions compile to WebAssembly (WASM) modules, enabling near-native execution speeds with strong security boundaries. A function that calculates complex discount logic executes in microseconds, not milliseconds.

Language Agnostic Write in Rust or JavaScript/TypeScript—both compile to the same WebAssembly runtime. Choose based on your team's expertise and performance requirements.

Stateless by Design Functions receive input, perform computation, and return output. They don't maintain state between calls, making them inherently scalable and predictable.

Version Controlled Deploy multiple versions of functions, run A/B tests, or roll back instantly. Each deployment is immutable and traceable.

How Shopify Functions Work

When a customer interacts with your store at a critical commerce moment, Shopify's system executes your function:

  1. Event Trigger: Customer action triggers a function event (adding item to cart, proceeding to checkout, selecting payment method)
  2. Input Preparation: Shopify collects relevant context (cart contents, customer location, order history) and serializes it
  3. Function Execution: Your compiled WebAssembly function runs with access to input data
  4. Output Processing: Function returns structured output (discount lines, shipping options, payment restrictions)
  5. Application: Shopify applies the function output to the current operation

This entire cycle completes in milliseconds—fast enough to not impact customer experience.

Functions vs. Apps vs. Scripts

AspectScriptsFunctionsApps
TechnologyLiquid template languageWebAssemblyREST API + React UI
Execution SpeedSlow (milliseconds+)Ultra-fast (microseconds)Very slow (API latency)
Use CaseTheme customizationCustom business logicAdmin interfaces, data storage
MaintenanceManual or deprecatedCLI-based, versionedDashboard-managed
ScalabilityLimitedUnlimitedAPI rate-limited
Code LocationTheme filesDedicated functionsRemote services
When to UseTheme-specific changesCustom discount/shipping/payment logicAdmin features, customer data needs

Shopify Functions Use Cases

1. Custom Discount Logic

The most powerful application of Shopify Functions is implementing complex discount systems without third-party apps.

Volume-Based Pricing

// Automatic tiered discounts based on total quantity
export function run(input) {
  const totalQuantity = input.cart.lines.reduce((sum, line) => sum + line.quantity, 0);

  let discountPercentage = 0;
  if (totalQuantity >= 100) discountPercentage = 20;
  else if (totalQuantity >= 50) discountPercentage = 15;
  else if (totalQuantity >= 20) discountPercentage = 10;

  if (discountPercentage === 0) return { discounts: [] };

  return {
    discounts: [{
      targets: { lineItems: { allLineItems: true } },
      value: {
        percentage: { value: discountPercentage.toString() }
      }
    }]
  };
}

Bundle Pricing Offer automatic discounts when customers purchase complementary products together. Function checks cart contents and applies discounts if specific product combinations exist.

Loyalty and Membership Discounts Access customer data to apply different discount rates based on membership tier, purchase history, or lifetime value—all without storing data in external systems.

Seasonal and Time-Based Discounts Implement discounts that vary by date, time of day, or customer location. A function running at checkout can instantly apply appropriate discounts based on real-time conditions.

First-Purchase Incentives Detect new customers and automatically apply welcome discounts—driving conversion without administrative overhead.

2. Dynamic Shipping Calculations

Shopify Functions enable shipping logic that adapts to business rules, operational constraints, and market conditions.

Real-Time Carrier Integration

pub fn run(input: FunctionInput) -> Result<FunctionResult, Box<dyn std::error::Error>> {
    let mut available_rates = vec![];

    for shipping_method in &input.cart.shipping_methods {
        let cost = calculate_dynamic_cost(
            shipping_method.code,
            input.cart.weight,
            &input.cart.shipping_address,
            &input.cart.items
        );

        available_rates.push(ShippingOption {
            title: shipping_method.label,
            code: shipping_method.code.clone(),
            cost: cost,
        });
    }

    Ok(FunctionResult {
        rates: available_rates,
    })
}

Geographic Pricing Charge different shipping rates based on destination country, state, or region. Hide shipping options unavailable to the customer's location.

Weight-Based Rates Calculate shipping costs dynamically based on actual package weight, including packaging materials.

Order Complexity Charges Add surcharges for orders with many line items, hazardous materials, or special handling requirements.

Carrier Integration Call external carrier APIs to fetch real-time rates and availability, then return the best options to customers.

3. Payment Customization

Control which payment methods are available based on business logic and customer context.

Geographic Payment Restrictions

export function run(input) {
  const country = input.customer?.address?.country;

  // Some payment methods only available in specific regions
  const availablePayments = {
    US: ['card', 'paypal', 'apple_pay'],
    JP: ['card', 'bank_transfer', 'convenience_store'],
    BR: ['card', 'pix', 'boleto'],
  };

  const allowed = availablePayments[country] || ['card'];

  return {
    payment_methods: allowed.map(method => ({
      method_name: method,
      available: true
    }))
  };
}

Customer Risk Assessment Restrict payment methods for high-risk customers or require verification for orders exceeding thresholds.

Product-Based Restrictions Disable certain payment methods for specific product categories. For example, some payment gateways don't support digital goods.

Subscription Payment Logic Customize payment options for subscription orders differently than one-time purchases.

WebAssembly and Performance Benefits

Why WebAssembly?

WebAssembly is a low-level bytecode format designed for efficient execution across platforms. When Shopify Functions compile to WASM, they gain several critical advantages:

Extreme Performance WebAssembly executes at near-native speeds (typically within 2-10% of equivalent C++ code). A discount calculation that takes 1ms in JavaScript runs in microseconds in WebAssembly—imperceptible to customers.

Minimal Cold Starts Unlike serverless functions that require runtime initialization, WebAssembly modules start instantly. Your function is ready to execute with zero warm-up time.

Memory Efficiency WASM modules use minimal memory footprint. Hundreds of concurrent functions can run simultaneously without memory pressure, enabling true global scalability.

Language Flexibility Write in Rust, JavaScript, Go, C++, or any language with WASM compilation support. Compile to the same bytecode runtime, so performance characteristics are consistent.

Security Isolation WASM runs in isolated sandboxes with explicit permission model. Functions can't access the file system, network, or other system resources unless explicitly granted.

Performance Comparison

OperationJavaScript (Node.js)Rust (Compiled to WASM)Improvement
Simple discount calculation2-3ms50-150μs20-60x faster
JSON parsing (10KB payload)5-8ms200-500μs15-40x faster
Algorithm (1000 iterations)15-25ms500μs-2ms10-50x faster
Function startup100-200ms<1ms100-200x faster

For a high-traffic Shopify store processing 1,000 checkout events per minute, WebAssembly means:

  • Rust WASM: 1,000 functions × 0.5ms = 500ms total CPU time
  • Node.js JavaScript: 1,000 functions × 5ms = 5,000ms total CPU time

Over an hour (60,000 checkouts), that's a difference between 30 seconds and 5 minutes of CPU time—directly impacting your infrastructure costs and checkout performance.

Development Workflow

Setting Up Your Development Environment

Prerequisites

  • Node.js 18+ or Rust 1.70+
  • Shopify CLI 3.49+
  • A Shopify app with function capability enabled
  • Git for version control

Installation

# Install Shopify CLI
npm install -g @shopify/cli

# Create a new Shopify app (if needed)
shopify app create node
cd my-shopify-app

# Generate a new function
shopify app function create
# Select function type: discount, shipping, or payment
# Choose language: javascript or rust

Local Development and Testing

Shopify provides tools for testing functions locally before deployment.

Testing with Mock Data

# Test your function with sample input
shopify function run --input 'input.json'

# View function logs
shopify function logs

Create test files that represent real-world scenarios:

// input.json - Sample discount function input
{
  "cart": {
    "lines": [
      {
        "id": "1",
        "quantity": 25,
        "cost": {
          "amountPerQuantity": {
            "amount": "10.00"
          }
        }
      }
    ]
  }
}

Debugging Use console logging in your function code—logs appear in CLI output and Shopify admin logs:

export function run(input) {
  console.log('Cart total items:', input.cart.lines.length);
  console.log('Customer location:', input.customer?.address?.country);
  // ... rest of logic
}

Deployment Pipeline

Step 1: Version and Commit

git add .
git commit -m "Update discount function for Q1 promotion"

Step 2: Deploy to Development Store

# Deploy all functions in your app
shopify app deploy

Step 3: Publish to Production

# Publish your app (functions deploy with it)
shopify app publish

Step 4: Monitor and Debug Access function logs through Shopify Admin: Settings → Apps and integrations → App history → View logs

Managing Multiple Versions

Deploy multiple versions of a function and test A/B variations:

# Tag current version
shopify function publish --version "v1.0.0"

# Deploy new version
# ... make changes ...
shopify function publish --version "v2.0.0"

# Switch traffic (in Shopify Admin)
Settings → Functions → Select version

Building Production Shopify Functions

Best Practices

1. Optimize for Speed Functions execute synchronously in the checkout flow. Every millisecond matters.

  • Avoid unnecessary loops and nested iterations
  • Cache computed values when possible
  • Use efficient algorithms (O(n) instead of O(n²))
  • Minimize JSON parsing overhead

2. Handle Edge Cases Gracefully Functions must handle unexpected input without failing:

export function run(input) {
  // Always validate input exists
  if (!input?.cart?.lines) {
    console.error('Invalid input structure');
    return { discounts: [] };
  }

  try {
    // Your logic here
  } catch (error) {
    console.error('Function error:', error);
    // Return safe default
    return { discounts: [] };
  }
}

3. Implement Clear Error Handling When logic fails, return sensible defaults rather than crashing:

  • Return empty discounts array if calculation fails
  • Return all shipping options if custom logic breaks
  • Allow payment method defaults if restriction fails

4. Test Thoroughly Create comprehensive test cases covering:

  • Valid inputs with various cart states
  • Edge cases (empty carts, single items, bulk orders)
  • Boundary conditions (discount thresholds, rate limits)
  • Error scenarios (invalid data, API failures)

5. Monitor Performance Track function execution metrics:

# View performance metrics
shopify function logs --tail

# Monitor in Shopify Admin
Orders → Select order → Timeline → View function execution

6. Document Your Logic Comments explain business rules for future maintainers:

// Apply 15% discount to orders >= $200 for returning customers
// Returning customers identified by having 2+ previous orders
export function run(input) {
  const previousOrderCount = input.customer?.orders?.length || 0;
  const cartTotal = calculateTotal(input.cart);

  if (previousOrderCount >= 2 && cartTotal >= 200) {
    return { discounts: [/* ... */] };
  }

  return { discounts: [] };
}

Integrating with Shopify Apps

Combine Functions with traditional Shopify apps for maximum capability. While Shopify functions provide the computational logic, apps can handle UI, data storage, and administration.

Architecture Example:

Shopify Store
├── Function (Discount Logic)
│   └── Executes at checkout
├── App (Admin Dashboard)
│   ├── Manage promotion rules
│   ├── View analytics
│   └── Configure settings
└── Database
    └── Store function configuration

Real-World Examples

Example 1: Volume-Based Bundle Discount

A home goods retailer wants to offer progressive discounts when customers buy multiple items:

  • 10% off when buying 3+ items
  • 15% off when buying 5+ items
  • 20% off when buying 10+ items

Implementation:

export function run(input) {
  const totalQuantity = input.cart.lines.reduce(
    (sum, line) => sum + line.quantity,
    0
  );

  let discountPercentage = 0;
  let discountReason = '';

  if (totalQuantity >= 10) {
    discountPercentage = 20;
    discountReason = 'Bulk purchase discount (10+ items)';
  } else if (totalQuantity >= 5) {
    discountPercentage = 15;
    discountReason = 'Volume discount (5+ items)';
  } else if (totalQuantity >= 3) {
    discountPercentage = 10;
    discountReason = 'Bundle discount (3+ items)';
  }

  if (discountPercentage === 0) {
    return { discounts: [] };
  }

  return {
    discounts: [{
      targets: {
        lineItems: { allLineItems: true }
      },
      value: {
        percentage: {
          value: discountPercentage.toString()
        }
      },
      message: discountReason
    }]
  };
}

Result: Customers see discounts applied automatically at checkout without needing coupon codes, increasing average order value.

Example 2: Region-Based Shipping Rates

An international retailer needs dynamic shipping costs based on destination:

  • USA: $5 base + $0.50 per pound
  • Canada: $15 base + $1.00 per pound
  • Europe: $25 base + $1.50 per pound
  • Rest of World: Unavailable

Implementation:

pub fn run(input: FunctionInput) -> Result<FunctionResult, Box<dyn std::error::Error>> {
    let shipping_address = &input.shipping_address;
    let weight = input.cart.weight;

    let (base_cost, per_pound) = match shipping_address.country_code.as_str() {
        "US" => (500, 50),           // $5.00 base, $0.50/lb
        "CA" => (1500, 100),         // $15.00 base, $1.00/lb
        "DE" | "FR" | "GB" => (2500, 150),  // $25.00 base, $1.50/lb
        _ => return Ok(FunctionResult { rates: vec![] }) // Not available
    };

    let total_cost = base_cost + (weight as i64 * per_pound);

    Ok(FunctionResult {
        rates: vec![
            ShippingRate {
                title: "Standard Shipping".to_string(),
                cost: total_cost,
            }
        ]
    })
}

Result: Shipping costs automatically adjust based on customer location and order weight, improving margin accuracy.

Example 3: Payment Method Restrictions

A subscription box service restricts payment methods based on risk:

  • Allow all methods for returning customers with positive history
  • Restrict to credit card and PayPal for new customers
  • Require verification for high-risk countries

Implementation:

export function run(input) {
  const customer = input.customer;
  const country = input.shipping_address.country;

  // Determine customer risk level
  const isReturningCustomer = (customer?.orders?.length || 0) > 0;
  const hasPositiveHistory = (customer?.lifetime_spent || 0) > 100;
  const isHighRiskCountry = ['KP', 'IR', 'SY'].includes(country);

  let allowedMethods = ['card', 'paypal'];

  if (isHighRiskCountry) {
    // High-risk requires verification
    if (!customer?.verified_email) {
      allowedMethods = ['card']; // Card only until verified
    }
  } else if (isReturningCustomer && hasPositiveHistory) {
    // Trusted customers get all methods
    allowedMethods = ['card', 'paypal', 'apple_pay', 'google_pay'];
  }

  return {
    payment_methods: allowedMethods.map(method => ({
      method_name: method,
      available: true
    }))
  };
}

Result: Risk profile automatically adjusts payment method availability, balancing security with customer experience.

Advanced Patterns

Combining Multiple Functions

Implement sophisticated logic by layering functions:

  1. Discount Function: Calculate available discounts based on cart contents
  2. Shipping Function: Calculate rates based on customer location and order weight
  3. Payment Function: Restrict payment methods based on region and risk

This layered approach keeps each function focused and maintainable.

Function Composition with External Data

While functions don't have native database access, you can:

  1. Store configuration data in your Shopify app
  2. Encode data as environment variables
  3. Include lookup tables in function code
  4. Cache reference data during deployment
const LOYALTY_TIERS = {
  'customer_1': { tier: 'gold', discount: 20 },
  'customer_2': { tier: 'silver', discount: 10 },
};

export function run(input) {
  const customerId = input.customer?.id;
  const tier = LOYALTY_TIERS[customerId];

  if (tier && tier.discount > 0) {
    // Apply loyalty discount
  }
}

A/B Testing Functions

Deploy multiple function versions and direct traffic to each:

# Version A: Old logic
shopify function publish --version "a-current"

# Version B: New logic
shopify function publish --version "b-test"

# In Shopify Admin, split traffic 50/50 between versions
Settings → Functions → A/B Testing

Monitor conversion rates and performance metrics for each version.

Performance Optimization Techniques

Minimize Data Processing

Only work with data you actually need:

// Bad: Process entire cart when you only need quantities
const allData = JSON.stringify(input.cart);
const cartSize = Object.keys(input.cart).length;

// Good: Extract only required data
const totalQuantity = input.cart.lines.reduce(
  (sum, line) => sum + line.quantity,
  0
);

Optimize Algorithms

Use efficient algorithms for calculations:

// Bad: O(n²) nested loops
for (let i = 0; i < discounts.length; i++) {
  for (let j = 0; j < cartItems.length; j++) {
    if (discounts[i].productId === cartItems[j].id) {
      // Apply discount
    }
  }
}

// Good: O(n) with Map for lookups
const discountMap = new Map(
  discounts.map(d => [d.productId, d])
);
cartItems.forEach(item => {
  const discount = discountMap.get(item.id);
  if (discount) {
    // Apply discount
  }
});

Use Rust for Performance-Critical Functions

When JavaScript performance isn't sufficient, write functions in Rust:

// Rust WASM is 10-50x faster than JavaScript for complex logic
pub fn calculate_discount(cart_items: &Vec<CartItem>) -> f64 {
    cart_items.iter()
        .map(|item| item.price * item.quantity as f64)
        .sum::<f64>() * 0.15
}

Monitoring and Debugging

Viewing Function Logs

# Stream logs in real-time
shopify function logs --tail

# View logs for specific function
shopify function logs discount --tail

# Export logs for analysis
shopify function logs --output json > logs.json

Common Issues and Solutions

Function Not Executing

  • Verify function is enabled in Shopify Admin
  • Check that function entry point is correct
  • Validate input/output schema matches function type

Slow Performance

  • Profile function execution time
  • Reduce algorithmic complexity
  • Cache computed values
  • Consider Rust for heavy computation

Unexpected Behavior

  • Add console.log statements
  • Test with actual cart data
  • Verify edge cases are handled
  • Check for logic errors in conditionals

Deploying Shopify Functions at Scale

Production Readiness Checklist

  • Function tested with representative data
  • Performance metrics acceptable (<10ms execution)
  • Error handling covers all edge cases
  • Code documented with business logic
  • Monitoring and alerting configured
  • Rollback plan documented
  • A/B testing strategy defined
  • Team trained on function operation

Monitoring Strategy

  1. Track execution time — Alert if functions exceed 50ms
  2. Monitor error rates — Alert if error rate > 1%
  3. Measure business impact — Track conversion rate changes
  4. Log customer feedback — Watch for checkout complaints

Scaling Considerations

Shopify's infrastructure automatically scales functions to handle traffic spikes. However:

  • Keep function execution <10ms to avoid checkout delays
  • Avoid expensive external API calls (use caching instead)
  • Test with production traffic volume before deploying
  • Monitor during sales events and peak traffic periods

Getting Started with Shopify Functions

Ready to build custom commerce logic? Here's the path forward:

Step 1: Understand Your Use Case What specific business logic do you need? Discounts, shipping, payments, or something custom?

Step 2: Explore the Shopify Developer Docs Visit the Shopify Functions documentation to understand your function type's input/output schema.

Step 3: Set Up Your Development Environment Install Shopify CLI and create your first function:

shopify app function create
shopify function run --input 'test.json'

Step 4: Deploy to a Development Store Test your function with real data before production:

shopify app deploy

Step 5: Monitor and Iterate Use Shopify Admin logs to monitor execution, performance, and errors. Iterate based on real-world behavior.

Optimizing Your E-commerce Performance

Beyond Shopify Functions, comprehensive store optimization requires attention to discovery, conversion, and operations. If you're looking to maximize your Shopify store's full potential, consider a complete audit of your current setup.

Get a free performance audit to identify opportunities in your checkout experience, product discovery, and operational efficiency.

Conclusion

Shopify Functions represent the evolution of commerce customization—moving from scripting within themes to building lean, fast, production-grade commerce logic that scales with your business. By understanding how to build, deploy, and monitor functions, you unlock capabilities that previously required expensive third-party apps or complex integrations.

The combination of WebAssembly execution speed, developer-friendly tooling, and Shopify's infrastructure reliability makes Functions the ideal choice for custom discount logic, dynamic shipping, and payment customization. Start small with a single function, measure the impact, and expand from there.

Next Steps:

  • Identify one process where custom logic would add value
  • Create a development environment and build a simple function
  • Deploy to a development store and test thoroughly
  • Monitor performance and iterate based on results
  • Expand to additional functions as you grow confidence

Have questions about implementing Shopify Functions for your specific use case? Reach out to our team for personalized guidance and support.


Keywords: Shopify Functions, WebAssembly, custom discount logic, dynamic shipping, payment customization, Shopify development, serverless functions, e-commerce development, Shopify app development, checkout customization

Ready to Dominate AI Search?

Get your free AI visibility audit and see how your brand appears across ChatGPT, Claude, and more.

Get Your Free Audit