Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.synapsebuilder.org/llms.txt

Use this file to discover all available pages before exploring further.

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