← Shopify App BuilderCONTENT HISTORY

Update to Shopify App Builder

Snapshot Sep 30, 2026 · 23:13 UTC · version 1.4.1

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "description": "Build WebAssembly functions for the Shopify checkout and order pipeline. Rust or JavaScript, 5ms execution window, 256KB binary limit. Targets cart transform, discount, validation, payment customization, delivery customization, order routing, fulfillment constraints, and localization. Triggers include: 'Shopify Function', 'WASM', 'Rust function', 'JavaScript function', 'function-runner', 'cart.transform.run', 'discount.run', 'cart.checkout-validation.run', 'cart.delivery-customization.run', 'cart.payment-customization.run', 'cart.lines.discounts.generate.run', 'order.routing.location.rank.run', 'fulfillment-constraints.run', 'localization.generate.run', 'metafield function', 'shopify function'.",
  "included_files": [],
  "name": "shopify-functions",
  "skill_md_contents": "---\nname: shopify-functions\ndescription: \"Build WebAssembly functions for the Shopify checkout and order pipeline. Rust or JavaScript, 5ms execution window, 256KB binary limit. Targets cart transform, discount, validation, payment customization, delivery customization, order routing, fulfillment constraints, and localization. Triggers include: 'Shopify Function', 'WASM', 'Rust function', 'JavaScript function', 'function-runner', 'cart.transform.run', 'discount.run', 'cart.checkout-validation.run', 'cart.delivery-customization.run', 'cart.payment-customization.run', 'cart.lines.discounts.generate.run', 'order.routing.location.rank.run', 'fulfillment-constraints.run', 'localization.generate.run', 'metafield function', 'shopify function'.\"\n---\n\n# Shopify Functions\n\nShopify Functions are WebAssembly (WASM) units of business logic that extend the Shopify checkout, order, and fulfillment pipelines. They execute in a sandboxed runtime on Shopify's servers and have strict constraints: 5ms execution window, 256KB binary limit, no async I/O, no outbound HTTP to unknown hosts, and 30-point GraphQL query complexity ceiling.\n\n## Architecture & Execution Model\n\n**Function Lifecycle:**\n1. Merchant installs your app; metafield definitions are registered\n2. App reads merchant configuration from metafields (Shop, Product, Collection scopes)\n3. On checkout/order event, Shopify invokes your function with Input JSON payload\n4. Function executes WASM bytecode, applies business logic, returns JSON output\n5. Output mutations are applied atomically to the checkout/order state\n\n**Execution Constraints (Hard Limits):**\n- Max execution time: 5ms (timeout failure = no operation)\n- Max binary size: 256KB gzipped\n- Max instructions: 11 million\n- Max memory: 256KB heap\n- Max output JSON: 256KB\n- Max GraphQL query complexity: 30 points (estimate 1pt per simple field)\n- No async I/O, no event loops, no multi-threading\n- No network access except allowed_hosts (NEW 2025-01)\n- Deterministic execution only\n\n**Why WASM?** Shopify Functions run in Wasmtime, a fast WebAssembly runtime. This provides:\n- Language flexibility: Compile Rust, JavaScript (via AssemblyScript), or Go to WASM\n- Isolation: No access to host filesystem, process, or network (except whitelisted hosts)\n- Performance: Near-native execution speed; optimized just-in-time compilation\n- Security: Sandboxed; input/output validation by Shopify platform\n\n## Function Targets & Checkout Pipeline\n\nThe Shopify checkout and order pipeline has **8 invocation points** (targets). Each target receives a specific input schema and must return a specific output schema:\n\n| Target | Phase | Purpose | Input | Output | Latency Budget |\n|--------|-------|---------|-------|--------|-----------------|\n| `cart.transform.run` | 1. Cart | Transform line items (bundle, rename, change quantity) | Cart items, metafields | Modified items | 5ms |\n| `cart.checkout-validation.run` | 2. Validation | Validate cart before payment (inventory, rules) | Cart state, attributes | Errors/blocks (optional) | 5ms |\n| `cart.delivery-customization.run` | 3. Delivery | Customize rates, hide options, rank (shipping, pickup) | Delivery options, cart | Customized rates/ranking | 5ms |\n| `cart.payment-customization.run` | 4. Payment | Hide payment methods, customize amounts | Payment methods, total | Customized methods/amounts | 5ms |\n| `discount.run` | 5. Discount | Apply discounts (% off, $ off, free shipping, gift) | Cart items, rules | Discount targets + value | 5ms |\n| `fulfillment-constraints.run` | 6. Fulfillment | Constrain what locations can fulfill each line | Cart items, locations | Location fulfillment rules | 5ms |\n| `order.routing.location.rank.run` | 7. Order Routing | Rank locations for fulfillment (priority, cost) | Locations, order lines | Ranked location order | 5ms |\n| `localization.generate.run` | 8. Localization | Generate translated/localized checkout labels | Buyer locale, shop context | Localized strings | 5ms |\n| `cart.lines.discounts.generate.run` | 5b. Line Discounts | NEW 2025+: Per-line discounts with allocation strategy | Line items, rules | Per-line discounts with allocation | 5ms |\n\n**Pipeline Execution:**\n1. Cart Transform → Validation → Delivery/Payment Customization → Discount → Fulfillment → Order Routing → Localization\n2. If any function returns error/blocks, pipeline halts and transaction fails\n3. All function outputs are applied transactionally; no partial states\n\n## Rust Project Structure (Recommended)\n\n**Shopify CLI v4.0.0+ generates this scaffold:**\n\n```bash\nshopify app function create --template rust --name my-function\n# Creates:\n# my-function/\n#   ├── Cargo.toml\n#   ├── src/\n#   │   ├── main.rs (entry point)\n#   │   └── input.graphql (input query)\n#   └── shopify.extension.toml (manifest)\n```\n\n**Cargo.toml (Rust 1.75+):**\n```toml\n[package]\nname = \"my-discount-function\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nshopify_function = { version = \"1.0\", features = [\"wasm\"] }\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\n\n[profile.release]\nopt-level = \"z\"\nlto = true\ncodegen-units = 1\nstrip = true\n\n[lib]\ncrate-type = [\"cdylib\"]\n```\n\n**src/main.rs (Discount Function Example):**\n```rust\nuse shopify_function::prelude::*;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Input {\n    pub cart: Cart,\n    pub metafield: Option<ConfigMetafield>,\n}\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Cart {\n    pub lines: Vec<CartLine>,\n    pub cost: CartCost,\n}\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct CartLine {\n    pub id: String,\n    pub quantity: i32,\n    pub cost: Cost,\n    pub merchandise: Merchandise,\n}\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Merchandise {\n    pub id: String,\n}\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Cost {\n    pub amount: String,\n}\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct CartCost {\n    pub subtotal_amount: String,\n}\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct ConfigMetafield {\n    pub value: String,\n}\n\n#[derive(Debug, Serialize)]\npub struct Output {\n    pub discounts: Vec<Discount>,\n    pub all_lines: bool,\n}\n\n#[derive(Debug, Serialize)]\npub struct Discount {\n    pub targets: Vec<Target>,\n    pub value: Value,\n    pub message: Option<String>,\n}\n\n#[derive(Debug, Serialize)]\npub struct Target {\n    pub line_item_group: LineItemGroup,\n}\n\n#[derive(Debug, Serialize)]\npub struct LineItemGroup {\n    pub id: String,\n}\n\n#[derive(Debug, Serialize)]\n#[serde(untagged)]\npub enum Value {\n    #[serde(rename_all = \"camelCase\")]\n    PercentageValue { percentage: String },\n    #[serde(rename_all = \"camelCase\")]\n    FixedAmountValue { fixed_amount: String },\n}\n\n#[shopify_function]\nfn discount(input: Input) -> FunctionResult<Output> {\n    let subtotal = input\n        .cart\n        .cost\n        .subtotal_amount\n        .parse::<f64>()\n        .unwrap_or(0.0);\n\n    if subtotal >= 100.0 {\n        return Ok(Output {\n            discounts: vec![Discount {\n                targets: input\n                    .cart\n                    .lines\n                    .iter()\n                    .map(|line| Target {\n                        line_item_group: LineItemGroup {\n                            id: line.id.clone(),\n                        },\n                    })\n                    .collect(),\n                value: Value::PercentageValue {\n                    percentage: \"10.0\".to_string(),\n                },\n                message: Some(\"10% off orders over $100\".to_string()),\n            }],\n            all_lines: true,\n        });\n    }\n\n    Ok(Output {\n        discounts: vec![],\n        all_lines: false,\n    })\n}\n```\n\n**src/input.graphql (Input Query for Discount Function):**\n```graphql\nquery Input {\n  cart {\n    lines {\n      id\n      quantity\n      cost {\n        amount\n      }\n      merchandise {\n        id\n      }\n    }\n    cost {\n      subtotal_amount\n    }\n  }\n  metafield(namespace: \"my-app\", key: \"discount-config\") {\n    value\n  }\n}\n```\n\n**shopify.extension.toml (Function Manifest):**\n```toml\nname = \"My Discount Function\"\ndescription = \"Applies percentage discount on orders over $100\"\n\ntype = \"function\"\napi_version = \"2025-01\"\n\n[[targets]]\ntarget = \"discount.run\"\n\n[[metafields]]\nnamespace = \"my-app\"\nkey = \"discount-config\"\ndescription = \"JSON config: {\\\"thresholdAmount\\\": 100, \\\"discountPercentage\\\": 10}\"\nowner_type = \"SHOP\"\n\n[[metafields]]\nnamespace = \"my-app\"\nkey = \"enabled\"\ndescription = \"Enable/disable discount\"\nowner_type = \"SHOP\"\n\n[network]\nallowed_hosts = [\"api.external-service.com\"]\n```\n\n**Build & Test:**\n```bash\ncd my-discount-function\nshopify app function build\n# Output: dist/index.wasm (gzipped, typically 50-100KB)\n\nshopify app function run --input input.json\n# Run locally with test payload\n```\n\n## JavaScript/TypeScript Project Structure\n\n**Scaffold (Remix app with TypeScript):**\n```bash\nshopify app function create --template javascript --name my-function\n```\n\n**package.json:**\n```json\n{\n  \"name\": \"my-cart-transform\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"build\": \"shopify app function build\",\n    \"test\": \"shopify app function run --input test/input.json\",\n    \"dev\": \"shopify app function run --watch\"\n  },\n  \"dependencies\": {\n    \"@shopify/function-runner\": \"^1.0.0\",\n    \"@shopify/type-generator\": \"^1.0.0\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.2.0\",\n    \"@types/node\": \"^20.0.0\"\n  }\n}\n```\n\n**src/run.ts (Cart Transform Example: Bundle Related Items):**\n```typescript\nimport { FunctionResult, TargetProduct } from \"@shopify/function-runner\";\n\ninterface Input {\n  cart: {\n    lines: Array<{\n      id: string;\n      quantity: number;\n      cost: { amount: string };\n      merchandise: { id: string; product?: { id: string; title: string } };\n    }>;\n  };\n  metafield?: { value: string };\n}\n\ninterface Output {\n  lines: Array<{\n    id: string;\n    quantity?: number;\n    merchandiseId?: string;\n  }>;\n  operations: Array<{\n    add?: {\n      merchandiseId: string;\n      quantity: number;\n    };\n    remove?: {\n      lineId: string;\n    };\n  }>;\n}\n\nexport default function run(input: Input): FunctionResult<Output> {\n  const bundleConfig = input.metafield\n    ? JSON.parse(input.metafield.value)\n    : { bundleName: \"Starter Pack\", items: [] };\n\n  const lines = input.cart.lines;\n  const operations: Output[\"operations\"] = [];\n\n  // Example: If cart has item A and item B, add item C at discount\n  const hasItemA = lines.some((l) => l.merchandise.id === \"gid://product/A\");\n  const hasItemB = lines.some((l) => l.merchandise.id === \"gid://product/B\");\n\n  if (hasItemA && hasItemB) {\n    operations.push({\n      add: {\n        merchandiseId: \"gid://product/C\",\n        quantity: 1,\n      },\n    });\n  }\n\n  return {\n    lines: lines.map((l) => ({ id: l.id })),\n    operations,\n  };\n}\n```\n\n**src/input.graphql:**\n```graphql\nquery Input {\n  cart {\n    lines {\n      id\n      quantity\n      cost {\n        amount\n      }\n      merchandise {\n        id\n        product {\n          id\n          title\n        }\n      }\n    }\n  }\n  metafield(namespace: \"my-app\", key: \"bundle-config\") {\n    value\n  }\n}\n```\n\n## Metafield Configuration Pattern\n\n**Define Metafield in shopify.extension.toml:**\n```toml\n[[metafields]]\nnamespace = \"my-app\"\nkey = \"discount-rules\"\ndescription = \"JSON: {\\\"thresholdAmount\\\": 100, \\\"percentage\\\": 10, \\\"enabled\\\": true}\"\nowner_type = \"SHOP\"\n\n[[metafields]]\nnamespace = \"my-app\"\nkey = \"product-discount-rules\"\ndescription = \"Product-specific discount config\"\nowner_type = \"PRODUCT\"\n\n[[metafields]]\nnamespace = \"my-app\"\nkey = \"collection-rules\"\ndescription = \"Collection-specific rules\"\nowner_type = \"COLLECTION\"\n```\n\n**Query Metafield in input.graphql:**\n```graphql\nquery Input {\n  shop {\n    id\n  }\n  metafield(namespace: \"my-app\", key: \"discount-rules\") {\n    value\n  }\n  cart {\n    lines {\n      id\n      merchandise {\n        id\n        product {\n          id\n          metafield(namespace: \"my-app\", key: \"product-discount-rules\") {\n            value\n          }\n          collections(first: 5) {\n            nodes {\n              id\n              metafield(namespace: \"my-app\", key: \"collection-rules\") {\n                value\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n\n**Parse & Use in Rust/JS Code:**\n```rust\n#[derive(Deserialize)]\nstruct DiscountConfig {\n    threshold_amount: f64,\n    percentage: f64,\n    enabled: bool,\n}\n\nlet config: DiscountConfig = serde_json::from_str(\n    input.metafield.as_ref().map(|m| m.value.as_str()).unwrap_or(\"{}\")\n)?;\n\nif !config.enabled {\n    return Ok(Output { discounts: vec![] });\n}\n\nif subtotal >= config.threshold_amount {\n    // Apply discount...\n}\n```\n\n## Network Access (NEW 2025-01)\n\n**Limited Outbound HTTP is Now Available:**\n\nFunctions can make HTTP requests to whitelisted hosts. This enables:\n- Real-time inventory checks from external systems\n- Currency conversion APIs\n- Machine learning model inference\n- Third-party rule engines\n\n**Declare Allowed Hosts in shopify.extension.toml:**\n```toml\n[network]\nallowed_hosts = [\n  \"api.inventory-service.com\",\n  \"ml-models.example.com\",\n  \"currency-api.service.io\"\n]\n```\n\n**Rust HTTP Example (using `reqwest` compiled to WASM):**\n```rust\nuse shopify_function::prelude::*;\n\n#[shopify_function]\nfn validate(input: Input) -> FunctionResult<Output> {\n    // Make HTTP call (sync only, no async/await in WASM)\n    let inventory_url = format!(\n        \"https://api.inventory-service.com/stock/{}\",\n        input.cart.lines[0].merchandise.id\n    );\n\n    // Note: Real WASM HTTP is still limited; most functions use metafield-driven rules\n    // True HTTP in functions is still experimental; verify with Shopify CLI\n\n    Ok(Output { /* ... */ })\n}\n```\n\n**Timeout & Fallback:**\n- HTTP requests timeout at 500ms (must complete within function's 5ms window if combined with other logic)\n- If HTTP fails, return safe default (e.g., allow delivery option, skip discount)\n- Never block checkout on external HTTP failure\n\n## Testing Functions\n\n**Test Input File (input.json):**\n```json\n{\n  \"cart\": {\n    \"lines\": [\n      {\n        \"id\": \"gid://shopify/CartLine/1\",\n        \"quantity\": 2,\n        \"cost\": {\n          \"amount\": \"150.00\"\n        },\n        \"merchandise\": {\n          \"id\": \"gid://shopify/ProductVariant/123\"\n        }\n      }\n    ],\n    \"cost\": {\n      \"subtotal_amount\": \"150.00\"\n    }\n  },\n  \"metafield\": {\n    \"value\": \"{\\\"thresholdAmount\\\": 100, \\\"percentage\\\": 10}\"\n  }\n}\n```\n\n**Run Function Locally:**\n```bash\nshopify app function run --input input.json\n\n# Output:\n# ✓ Function executed successfully\n# {\n#   \"discounts\": [\n#     {\n#       \"targets\": [{ \"lineItemGroup\": { \"id\": \"gid://shopify/CartLine/1\" } }],\n#       \"value\": { \"percentage\": \"10.0\" },\n#       \"message\": \"10% off orders over $100\"\n#     }\n#   ],\n#   \"all_lines\": true\n# }\n```\n\n**Replay Recorded Invocations:**\n```bash\nshopify app function run --replay\n# Re-run against real checkout data captured from production\n```\n\n**Explain Query Complexity:**\n```bash\nshopify app function explain-query\n# Analyzes input.graphql and reports complexity score (max 30)\n```\n\n**Golden Tests Pattern (Recommended):**\nCreate test cases in `tests/` directory:\n\n```rust\n// tests/discount_test.rs\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_discount_applied_over_100() {\n        let input = Input {\n            cart: Cart {\n                lines: vec![CartLine {\n                    id: \"line1\".to_string(),\n                    quantity: 1,\n                    cost: Cost {\n                        amount: \"150.00\".to_string(),\n                    },\n                    merchandise: Merchandise {\n                        id: \"variant1\".to_string(),\n                    },\n                }],\n                cost: CartCost {\n                    subtotal_amount: \"150.00\".to_string(),\n                },\n            },\n            metafield: None,\n        };\n\n        let result = discount(input).unwrap();\n        assert_eq!(result.discounts.len(), 1);\n        assert_eq!(result.discounts[0].value, Value::PercentageValue { percentage: \"10.0\".to_string() });\n    }\n\n    #[test]\n    fn test_no_discount_under_100() {\n        let input = Input {\n            cart: Cart {\n                lines: vec![],\n                cost: CartCost {\n                    subtotal_amount: \"50.00\".to_string(),\n                },\n            },\n            metafield: None,\n        };\n\n        let result = discount(input).unwrap();\n        assert_eq!(result.discounts.len(), 0);\n    }\n}\n```\n\nRun tests:\n```bash\ncargo test\n```\n\n## Full Working Examples\n\n**Example 1: Rust Discount Function (10% off orders > $100)**\n\nFile structure:\n```\nrust-discount/\n├── Cargo.toml\n├── shopify.extension.toml\n├── src/\n│   ├── main.rs\n│   └── input.graphql\n└── tests/\n    └── discount_test.rs\n```\n\n`Cargo.toml`:\n```toml\n[package]\nname = \"rust-discount\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nshopify_function = \"1.0\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\n\n[profile.release]\nopt-level = \"z\"\nlto = true\nstrip = true\n```\n\n`src/main.rs`:\n```rust\nuse shopify_function::prelude::*;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize)]\npub struct Input {\n    pub cart: Cart,\n}\n\n#[derive(Debug, Deserialize)]\npub struct Cart {\n    pub lines: Vec<CartLine>,\n    pub cost: CartCost,\n}\n\n#[derive(Debug, Deserialize)]\npub struct CartLine {\n    pub id: String,\n}\n\n#[derive(Debug, Deserialize)]\npub struct CartCost {\n    pub subtotal_amount: String,\n}\n\n#[derive(Debug, Serialize)]\npub struct Output {\n    pub discounts: Vec<Discount>,\n    pub all_lines: bool,\n}\n\n#[derive(Debug, Serialize)]\npub struct Discount {\n    pub targets: Vec<Target>,\n    pub value: DiscountValue,\n    pub message: Option<String>,\n}\n\n#[derive(Debug, Serialize)]\npub struct Target {\n    pub line_item_group: LineItemGroup,\n}\n\n#[derive(Debug, Serialize)]\npub struct LineItemGroup {\n    pub id: String,\n}\n\n#[derive(Debug, Serialize)]\n#[serde(untagged)]\npub enum DiscountValue {\n    Percentage { percentage: String },\n}\n\n#[shopify_function]\nfn discount(input: Input) -> FunctionResult<Output> {\n    let subtotal = input.cart.cost.subtotal_amount.parse::<f64>().unwrap_or(0.0);\n\n    if subtotal >= 100.0 {\n        return Ok(Output {\n            discounts: vec![Discount {\n                targets: input.cart.lines.iter().map(|line| Target {\n                    line_item_group: LineItemGroup { id: line.id.clone() },\n                }).collect(),\n                value: DiscountValue::Percentage { percentage: \"10.0\".to_string() },\n                message: Some(\"10% off orders over $100\".to_string()),\n            }],\n            all_lines: true,\n        });\n    }\n\n    Ok(Output { discounts: vec![], all_lines: false })\n}\n```\n\n**Example 2: JavaScript Cart Transform (Auto-Add Bundle Item)**\n\n`src/run.ts`:\n```typescript\nexport default function run(input) {\n  const cart = input.cart;\n  const operations = [];\n\n  // If cart has specific product, auto-add complementary item\n  const hasMainProduct = cart.lines.some(\n    (line) => line.merchandise.id === \"gid://shopify/ProductVariant/main123\"\n  );\n\n  if (hasMainProduct && cart.lines.length === 1) {\n    operations.push({\n      add: {\n        merchandiseId: \"gid://shopify/ProductVariant/bundle456\",\n        quantity: 1,\n      },\n    });\n  }\n\n  return {\n    lines: cart.lines.map((line) => ({ id: line.id })),\n    operations,\n  };\n}\n```\n\n**Example 3: Rust Validation Function (Check Inventory)**\n\n`src/main.rs`:\n```rust\nuse shopify_function::prelude::*;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize)]\npub struct Input {\n    pub cart: Cart,\n}\n\n#[derive(Debug, Deserialize)]\npub struct Cart {\n    pub lines: Vec<CartLine>,\n}\n\n#[derive(Debug, Deserialize)]\npub struct CartLine {\n    pub quantity: i32,\n    pub merchandise: Merchandise,\n}\n\n#[derive(Debug, Deserialize)]\npub struct Merchandise {\n    pub id: String,\n}\n\n#[derive(Debug, Serialize)]\npub struct Output {\n    pub errors: Vec<ValidationError>,\n}\n\n#[derive(Debug, Serialize)]\npub struct ValidationError {\n    pub message: String,\n    pub target: String,\n}\n\n#[shopify_function]\nfn validate(input: Input) -> FunctionResult<Output> {\n    let mut errors = vec![];\n\n    for line in &input.cart.lines {\n        // Mock inventory check\n        if line.quantity > 10 {\n            errors.push(ValidationError {\n                message: \"Quantity exceeds available inventory\".to_string(),\n                target: line.merchandise.id.clone(),\n            });\n        }\n    }\n\n    Ok(Output { errors })\n}\n```\n\n## Deployment & Verification\n\n**Build & Deploy:**\n```bash\n# 1. Build WASM binary\nshopify app function build\n\n# 2. Verify binary size\nls -lh dist/index.wasm\n# Should be <256KB (gzipped)\n\n# 3. Test against sample input\nshopify app function run --input test-input.json\n\n# 4. Deploy with app\nshopify app deploy\n# Shopify CLI creates function version + deploys extension\n\n# 5. Enable function in Shopify Admin\n# Apps > Your App > Functions > [Function Name] > Enable\n```\n\n**Monitor Function Health:**\n- Shopify Admin: Apps > Your App > Functions > [Name] > Metrics\n- Track: execution count, error rate, latency percentiles\n- Set alerts: >5% error rate, >90th percentile latency >3ms\n\n**Version Management:**\n- Functions are versioned by Shopify CLI deployment timestamp\n- Active version runs on all new checkouts\n- Rollback: Admin > Functions > [Name] > Versions > Select Previous\n- No breaking changes: Always ship backward-compatible input/output\n\n## Decision Tree: Which Function Target?\n\n```\nWhat do you need to do?\n\n├─ Transform cart items (bundle, rename, remove)?\n│  └─ target: cart.transform.run\n│     Input: cart.lines (id, quantity, merchandise)\n│     Output: modified lines\n\n├─ Validate cart before checkout (inventory, rules)?\n│  └─ target: cart.checkout-validation.run\n│     Input: cart state\n│     Output: validation errors (blocks checkout if present)\n\n├─ Customize delivery options (hide, rate override)?\n│  └─ target: cart.delivery-customization.run\n│     Input: deliveryOptions[]\n│     Output: customized/ranked options\n\n├─ Hide payment methods or customize amounts?\n│  └─ target: cart.payment-customization.run\n│     Input: paymentMethods[]\n│     Output: customized methods\n\n├─ Apply discounts (% off, $ off, free shipping)?\n│  └─ target: discount.run\n│     Input: cart state, metafield config\n│     Output: discount[] with targets & value\n\n├─ Apply per-line discounts (2025+)?\n│  └─ target: cart.lines.discounts.generate.run\n│     Input: line items\n│     Output: per-line discount with allocation\n\n├─ Constrain which locations can fulfill items?\n│  └─ target: fulfillment-constraints.run\n│     Input: locations[], cart.lines\n│     Output: fulfillment rules\n\n├─ Rank locations for order routing (cost, priority)?\n│  └─ target: order.routing.location.rank.run\n│     Input: locations[], order\n│     Output: ranked location[] order\n\n└─ Generate localized checkout labels?\n   └─ target: localization.generate.run\n      Input: buyer locale, shop context\n      Output: localized strings\n```\n\n## Troubleshooting & Common Issues\n\n| Issue | Root Cause | Fix |\n|-------|-----------|-----|\n| **WASM binary exceeds 256KB** | Heavy dependencies, unoptimized build | Set `opt-level = \"z\"`, `lto = true`, `strip = true` in Cargo.toml; remove unused deps; use `wasm-opt` post-processor |\n| **Function timeout (5ms exceeded)** | Complex GraphQL query (30+ points) or heavy loop logic | Simplify input query; pre-aggregate in metafield; reduce loop iterations; profile with `shopify app function run --explain-query` |\n| **Syntax error in input.graphql** | Invalid field names or nesting | Verify schema against latest API version; use `shopify app function explain-query` to validate |\n| **Metafield returns null/empty** | Metafield not set on Shop/Product/Collection | Check Admin > Settings > Custom data; confirm namespace/key match shopify.extension.toml; set test value |\n| **Discount doesn't apply in checkout** | Function returns OK but no discount output | Verify function is enabled in Admin > Apps > Your App > Functions; check discount logic (threshold check, target IDs match) |\n| **Cart transform operation fails** | Invalid merchandiseId or operation structure | Verify merchandiseId format (gid://shopify/ProductVariant/XXX); check input query includes variant IDs; test with `shopify app function run` first |\n| **Error: \"Output exceeds 256KB\"** | Returning too much data | Reduce output fields; compress message strings; avoid returning full cart state |\n| **Validation function blocks all checkouts** | Always returning errors in Output | Add condition to only return errors when validation fails; default to empty errors[] |\n| **Network request from function fails silently** | HTTP request to non-whitelisted host | Add host to `[network] allowed_hosts` in shopify.extension.toml; verify DNS resolution |\n| **Graphql query complexity > 30 points** | Too many fields or nested selections | Remove unnecessary fields from input.graphql; use aliases to reduce redundant queries; check Admin API complexity docs |\n| **Function runs but output ignored** | Wrong output format or missing required field** | Verify output JSON schema matches target spec (e.g., Discount must have targets[], value); test against sample input |\n| **Type mismatch in Rust/TS** | Serde/TypeScript serialization error | Ensure struct field names match GraphQL response (snake_case vs camelCase); add #[serde(rename)] if needed |\n\n## Performance Optimization\n\n**Binary Size Reduction:**\n```toml\n[profile.release]\nopt-level = \"z\"        # Maximum size optimization\nlto = true             # Link-time optimization\ncodegen-units = 1      # Single codegen unit for better optimization\nstrip = true           # Strip debug symbols\npanic = \"abort\"        # Use abort instead of unwind\n```\n\n**Query Optimization:**\n- Request only fields needed for logic (each field ≈ 1 complexity point)\n- Move repeated queries to metafield (query once, store in metadata)\n- Use `first: 1` or `first: 5` limits instead of full collections\n- Combine related fields into single query rather than separate queries\n\n**Code Optimization (Rust):**\n- Use `&str` instead of `String` where possible\n- Pre-allocate Vec capacity if size is known\n- Avoid cloning; use references\n- Profile with `wasm-opt` post-processor:\n  ```bash\n  cargo install wasm-opt\n  wasm-opt -Oz dist/index.wasm -o dist/index.wasm\n  ```\n\n**Output Optimization:**\n- Serialize only required fields\n- Use compact JSON (no whitespace)\n- Limit discount message length\n- Pre-compute values before serialization\n\n## API Version & Changelog\n\n**Version note:** The examples below document capabilities introduced in `2025-01`. Use the latest stable version supported by the specific Function target when creating a new extension.\n\n**2025-01 New Features:**\n- Network access via `allowed_hosts` declaration\n- Per-line discounts via `cart.lines.discounts.generate.run`\n- Improved error messages in function runtime\n- Function async/await still NOT supported; purely synchronous\n\n**2024-10 (Previous):**\n- Original 8 function targets stable\n- Metafield support\n- GraphQL query complexity ceiling (30 points)\n\n**Upgrading:**\n```toml\n# In shopify.extension.toml\napi_version = \"2026-07\"  # Confirm the latest version supported by this Function target\n```\n\nFunctions written for 2024-10 continue to work in 2025-01; no breaking changes.\n\n## Resources\n\n- **Shopify Functions Docs:** https://shopify.dev/docs/apps/functions\n- **GraphQL Admin API:** https://shopify.dev/docs/api/admin-graphql/latest\n- **CLI Reference:** `shopify app function --help`\n- **WASM in Rust:** https://www.rust-lang.org/what/wasm/\n- **Shopify Community:** https://community.shopify.com/c/shopify-apis-sdks/ct-p/apis-sdks\n"
}

SHA-256 of public snapshot: 300b07015c58dbc6feba7194d2b830e2b1d7f6c197a80f5d4444a6cd0ebcab59