Skip to main content

Shopify Rust Functions Guide

Overview

Shopify Functions run on Shopify’s infrastructure as WebAssembly (WASM) modules. They must be written in Rust using the shopify_function crate.

Function Structure

Basic Discount Function

use shopify_function::prelude::*;
use shopify_function::Result;

// Input/Output types generated from GraphQL schema
use super::input::*;
use super::output::*;

#[shopify_function]
fn run(input: input::ResponseData) -> Result<output::FunctionResult> {
    // Parse configuration from metafield
    let config = parse_configuration(&input)?;
    
    // Business logic
    let discount_percentage = config.percentage.unwrap_or(10.0);
    let minimum_amount = config.minimum_amount.unwrap_or(0.0);
    
    // Check cart total
    let cart_total = input.cart.cost.subtotal_amount.amount;
    
    if cart_total < minimum_amount {
        // No discount - return empty result
        return Ok(output::FunctionResult {
            discounts: vec![],
            discount_application_strategy: output::DiscountApplicationStrategy::FIRST,
        });
    }
    
    // Apply discount
    let targets = vec![output::Target {
        product_variant: Some(output::ProductVariantTarget {
            id: input.cart.lines[0].merchandise.id.clone(),
            quantity: None,
        }),
    }];
    
    Ok(output::FunctionResult {
        discounts: vec![output::Discount {
            message: Some(format!("{}% off", discount_percentage)),
            targets,
            value: output::Value {
                percentage: Some(output::Percentage {
                    value: discount_percentage.to_string(),
                }),
                fixed_amount: None,
            },
        }],
        discount_application_strategy: output::DiscountApplicationStrategy::FIRST,
    })
}

// Helper to parse configuration JSON from metafield
fn parse_configuration(input: &input::ResponseData) -> Result<Configuration> {
    let config_value = input
        .discount_node
        .metafield
        .as_ref()
        .and_then(|m| m.value.as_ref())
        .ok_or("Missing configuration")?;
    
    serde_json::from_str(config_value)
        .map_err(|_| "Invalid configuration JSON".into())
}

#[derive(serde::Deserialize)]
struct Configuration {
    percentage: Option<f64>,
    minimum_amount: Option<f64>,
}

Function Types

1. Product Discount Function

Target: purchase.product-discount.run Use Case: Apply discounts to specific products/variants

2. Order Discount Function

Target: purchase.order-discount.run Use Case: Apply discounts to entire order

3. Shipping Discount Function

Target: purchase.shipping-discount.run Use Case: Modify shipping rates

4. Payment Customization

Target: purchase.payment-customization.run Use Case: Hide/rename payment methods

5. Delivery Customization

Target: purchase.delivery-customization.run Use Case: Modify delivery options

6. Cart/Checkout Validation

Target: purchase.validation.run Use Case: Block checkout based on conditions

Input Query Examples

Discount Function Input

query RunInput {
  discountNode {
    metafield(namespace: "$app:function-configuration", key: "function-configuration") {
      value
    }
  }
  cart {
    cost {
      subtotalAmount {
        amount
      }
    }
    lines {
      merchandise {
        ... on ProductVariant {
          id
        }
      }
    }
  }
}

Payment Customization Input

query RunInput {
  cart {
    cost {
      subtotalAmount {
        amount
      }
    }
  }
  paymentMethods {
    id
    name
  }
}

Cargo.toml Template

[package]
name = "function_name"
version = "0.1.0"
edition = "2021"

[dependencies]
shopify_function = "1.3.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[profile.release]
lto = true
opt-level = "z"
strip = true

Extension TOML Template

api_version = "2025-01"

[[extensions]]
name = "function-name"
handle = "function-name"
type = "function"
description = "Function description"

  [[extensions.targeting]]
  target = "purchase.product-discount.run"
  input_query = "src/run.graphql"
  export = "run"

Common Patterns

1. Percentage Discount

value: output::Value {
    percentage: Some(output::Percentage {
        value: "10.0".to_string(),
    }),
    fixed_amount: None,
}

2. Fixed Amount Discount

value: output::Value {
    percentage: None,
    fixed_amount: Some(output::FixedAmount {
        amount: "5.00".to_string(),
    }),
}

3. Conditional Logic

if cart_total > 100.0 {
    // Apply 15% discount
} else if cart_total > 50.0 {
    // Apply 10% discount
} else {
    // No discount
}

4. Customer Segment Checking

let is_vip = input.customer
    .and_then(|c| c.tags)
    .map(|tags| tags.contains(&"VIP".to_string()))
    .unwrap_or(false);

Deployment Flow

  1. Generate Rust Code: AI generates src/run.rs
  2. Create GraphQL Query: Define input schema in src/run.graphql
  3. Add Cargo.toml: Rust package configuration
  4. GitHub Actions:
    • Installs Rust toolchain
    • Compiles to wasm32-wasi target
    • Optimizes with wasm-opt
  5. Shopify CLI: Deploys WASM module to Shopify

Testing Locally

# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Add WASM target
rustup target add wasm32-wasi

# Build function
cd extensions/your-function
cargo build --target wasm32-wasi --release

# Test with Shopify CLI
shopify app function run

Best Practices

  1. Keep Functions Fast: < 10ms execution time
  2. Handle Missing Data: Use .unwrap_or() and error handling
  3. Validate Input: Check configuration and cart data
  4. Clear Messages: Provide descriptive discount messages
  5. Test Edge Cases: Empty cart, zero prices, missing fields

Resources