Shopify Functions vs. Legacy Scripts: The Complete 2026 Migration Guide
If you are running a Shopify Plus store in 2026, you likely rely on Shopify Scripts (Ruby) for your complex tiered pricing, bundles, or shipping logic. But the writing is on the wall: Scripts are legacy technology. The future belongs to Shopify Functions (Wasm). This comprehensive guide covers everything you need to know about migrating, including architecture differences, code examples, debugging strategies, and real-world implementation patterns.
This is not just a syntax change. It is a fundamental architecture shift from "scripting on the fly" to "deployed compiled logic." It affects how you develop, test, and deploy customization logic across your entire checkout experience.
The Deprecation Reality
Shopify is actively pushing merchants off Scripts. Checkout Extensibility does not support the old Script Editor. If you want the new high-performance checkout with features like one-page checkout, Shop Pay optimizations, and future enhancements, you must rewrite your Scripts as Functions. There is no workaround.
Complete Migration Guide Contents
- 1. What Are Shopify Scripts? (The Legacy System)
- 2. What Are Shopify Functions? (The New Standard)
- 3. The Architecture Shift: Ruby vs. WebAssembly
- 4. Complete Feature Comparison Table
- 5. Types of Shopify Functions Available
- 6. Code Comparison: Script vs. Function Examples
- 7. Rust vs. JavaScript: Which Should You Choose?
- 8. Comprehensive Pros & Cons Analysis
- 9. Real-World Use Cases and Implementation Patterns
- 10. Step-by-Step Migration Plan
- 11. Debugging and Testing Strategies
- 12. Performance Benchmarks and Optimization
- 13. Common Migration Mistakes to Avoid
- 14. Frequently Asked Questions (15+ FAQs)
- 15. Conclusion and Next Steps
1. What Are Shopify Scripts? (The Legacy System)
Shopify Scripts were introduced as a Shopify Plus exclusive feature that allowed merchants to write custom Ruby code to modify cart behavior, shipping options, and payment gateways at checkout. The three types of Scripts available were:
- Line Item Scripts: Modify cart contents, apply discounts to specific products, create BOGO offers, tier pricing, and bundle discounts.
- Shipping Scripts: Hide, rename, or reorder shipping methods. Apply free shipping conditionally. Set minimum order values for specific shipping options.
- Payment Scripts: Hide or rename payment gateways based on customer tags, cart total, or product properties. Useful for B2B scenarios.
How Scripts Worked Technically
When a customer reached checkout, Shopify would execute your Ruby code in a sandboxed mruby environment. The script received an "Input" object containing cart data and returned a modified "Output" object. This happened synchronously, which meant:
- Complex scripts added latency to checkout
- CPU and memory limits could cause scripts to fail silently
- No way to unit test scripts outside of production
- Version control was difficult (copy-pasting into an editor)
While this code looks simple, the underlying infrastructure was becoming a liability. Every time Shopify wanted to improve checkout (like the new one-page checkout), they had to ensure backward compatibility with thousands of custom Ruby scripts. This slowed innovation.
2. What Are Shopify Functions? (The New Standard)
Shopify Functions are the replacement for Scripts. Instead of writing Ruby code in a web editor, you write code locally (in Rust or JavaScript), compile it to WebAssembly (Wasm), and deploy it as part of a Shopify App. The function is then "installed" on your store and runs at specific extension points.
Key Characteristics of Functions
- Compiled, Not Interpreted: Functions are pre-compiled to Wasm. This means execution is near-instantaneous (single-digit milliseconds).
- App-Based: A Function lives inside a Shopify App. You deploy the app to your store. The app can contain multiple functions for different purposes.
- Type-Safe: Both Rust and the JavaScript tooling (Javy) provide strong typing. Errors are caught at compile time, not runtime.
- Configurable by Merchants: Unlike hardcoded Script variables, Functions can expose Metafields that merchants edit in the Shopify Admin without touching code.
- Testable: You can write unit tests locally. You can simulate input payloads. You can CI/CD your discount logic.
The Conceptual Model
Think of Functions as "microservices for checkout logic." Each Function has a specific job:
- It receives a structured JSON input (cart data, customer data, etc.)
- It processes this input according to your business logic
- It returns a structured JSON output (discounts to apply, shipping methods to hide, etc.)
Shopify's infrastructure handles calling your Function at the right moment, passing the right data, and applying the returned output.
3. The Architecture Shift: Ruby vs. WebAssembly
Understanding the architectural differences is crucial for a successful migration. This is not just a language swap; it's a paradigm change.
Legacy Architecture (Scripts)
- Merchant writes Ruby code in the Script Editor app (browser-based)
- Code is stored as text in Shopify's database
- At checkout, Shopify spins up a sandboxed mruby interpreter
- The interpreter parses and executes the Ruby code
- Results are applied to the cart
Problems: Interpretation adds latency. Sandboxing is complex and limits capabilities. No external API calls allowed. No way to share logic between scripts.
New Architecture (Functions)
- Developer writes code in Rust or JavaScript locally
- Code is compiled to WebAssembly (a binary format)
- The Wasm binary is deployed to Shopify via an App
- At checkout, Shopify calls the pre-compiled Wasm binary
- Execution happens in microseconds
- Results are returned and applied
Benefits: Near-zero latency (5ms or less). Portable and secure by design. Functions can be versioned, tested, and deployed via CI/CD. Merchant-facing configuration via Metafields.
4. Complete Feature Comparison Table
Here is an exhaustive comparison between the two systems as of January 2026:
| Feature | Legacy Scripts (Ruby) | Shopify Functions (Wasm) |
|---|---|---|
| Languages Supported | Ruby (mruby subset) | Rust, JavaScript, TypeScript, AssemblyScript |
| Deployment Method | Copy/paste into Script Editor | CLI push (part of App deployment) |
| Execution Time | 10-100ms (interpreted) | 1-5ms (compiled Wasm) |
| Local Development | Not supported | Full local dev with Shopify CLI |
| Unit Testing | Not possible | Standard testing frameworks supported |
| Version Control | Manual (external) | Git-native |
| Merchant Configuration | Hardcoded in script | Metafields UI in Admin |
| API Access | None (sandboxed) | None (sandboxed, but can use network for pre-fetch) |
| Checkout Extensibility | Not compatible | Fully compatible |
| Discount Stacking Control | Manual code required | Native "Discount Combinations" settings |
| Error Handling | Silent failures | Logged to Partner Dashboard |
| Future Support | Deprecated (sunset planned) | Actively developed |
5. Types of Shopify Functions Available
Shopify Functions are not monolithic. Different "APIs" exist for different customization points. Understanding which API to use is critical for your migration.
5.1 Product Discount API
Replaces Line Item Scripts for product-level discounts. Applies percentage or fixed discounts to specific products or variants based on conditions you define.
- Use for: BOGO, tiered quantity discounts, category sales
- Input: Cart lines, product metafields, customer
- Output: List of discounts with targets
5.2 Order Discount API
Applies discounts at the order level (not per-line). Think "Spend $100, get 10% off your order."
- Use for: Threshold-based order discounts, loyalty discounts
- Input: Cart total, customer tags
- Output: Discount percentage or fixed amount
5.3 Delivery Customization API
Replaces Shipping Scripts. Hides, renames, or reorders shipping methods shown to the customer.
- Use for: Hiding expedited shipping below $50, renaming "Ground" to "Eco-Friendly", sorting cheapest first
- Input: Delivery options from carriers, cart data
- Output: Modified delivery options list
5.4 Payment Customization API
Replaces Payment Scripts. Hides or renames payment methods at checkout.
- Use for: Hiding COD for high-value orders, restricting PayPal for specific countries
- Input: Payment methods, cart, customer location
- Output: Modified payment methods list
5.5 Cart Transform API
A newer API that allows modifying cart lines before checkout. Useful for bundling, automatic add-ons, and cart splitting.
- Use for: "Add free gift over $100", split subscription items, bundle components
- Input: Cart lines
- Output: Cart operations (merge, expand, update)
5.6 Fulfillment Constraints API
Allows you to set constraints on how orders can be fulfilled. Useful for pickup-only items or location-restricted products.
6. Code Comparison: Script vs. Function Examples
Let's look at multiple real-world examples translated from Scripts to Functions.
Example 1: Spend $100, Get 10% Off (Order Discount)
Legacy Script (Ruby)
Shopify Function (JavaScript)
Key Difference: The Function returns a structured object. Shopify interprets this object and applies the discount. You don't mutate the cart directly.
Example 2: VIP Customer Discount (Product Discount)
Legacy Script (Ruby)
Shopify Function (Rust)
Complexity Note: The Rust version is more verbose because it's type-safe. Every field is explicitly handled. This makes errors obvious at compile time, but requires more upfront work.
Example 3: Hide Shipping Method Below Threshold
Legacy Script (Ruby)
Shopify Function (JavaScript)
7. Rust vs. JavaScript: Which Should You Choose?
This is the question I get asked most frequently. The answer depends on your team and use case.
Use JavaScript If:
- Your team already knows JavaScript/TypeScript
- Your logic is relatively straightforward (thresholds, customer tags)
- You want faster initial development
- You're comfortable with dynamic typing (though TS helps)
- Performance is "good enough" (most cases)
Use Rust If:
- You have extremely complex logic (nested conditions, loops)
- You need maximum performance (high-volume flash sales)
- Your team has Rust experience or is willing to learn
- You want compile-time guarantees (fewer runtime surprises)
- You're building reusable logic for multiple clients
My Recommendation: For 90% of Plus merchants, JavaScript (via Javy) is sufficient and significantly faster to develop. Rust is overkill unless you're an agency building reusable app components or handling extreme scale.
8. Comprehensive Pros & Cons Analysis
Advantages of Migrating to Functions
- Future-Proofing: Functions are the only path forward. Scripts will eventually stop working. The longer you wait, the more urgent (and expensive) the migration becomes.
- Performance: Wasm execution is 10-100x faster than interpreted Ruby. This directly impacts checkout speed and conversion rate.
- Testability: You can write unit tests for your discount logic. You can run them in CI. You can catch bugs before they cost you money.
- Version Control: Your function code lives in Git. You can track changes, roll back, and collaborate with a team properly.
- Merchant Empowerment: Expose configuration (discount percentages, thresholds) via Metafields. Marketing can tweak promos without a developer.
- Access to New Features: Checkout Extensibility features (like UI Extensions, branding APIs, post-purchase offers) only work with Functions.
Disadvantages and Challenges
- Learning Curve: If your team only knows Ruby, learning Rust is non-trivial. JavaScript is easier but still requires understanding the Shopify Functions framework.
- Setup Overhead: You need a Partner Dashboard app, Shopify CLI installed, local environment configured. This is more work than copy-pasting into a web editor.
- Debugging: Logs appear in the Partner Dashboard, not inline with your store. Correlating errors to specific checkouts can be tricky.
- App Management: Your function is part of an App. If you have many Functions, you need to manage them as an app portfolio, not loose scripts.
- No Direct Cart Mutation: You can't "just change the price" like in Scripts. You return instructions, and Shopify applies them. This requires a mental model shift.
9. Real-World Use Cases and Implementation Patterns
Use Case 1: Tiered Quantity Discount
Scenario: Buy 5-9 units, get 10% off. Buy 10-19 units, get 15% off. Buy 20+, get 25% off.
Legacy Script: A series of if/elsif statements checking quantity ranges and mutating line prices.
Function Implementation: Use a Product Discount Function. Read an array of tier definitions from Metafields (so marketing can adjust tiers). Loop through cart lines, match quantities to tiers, return discount targets.
Benefit: Merchants can edit tiers (e.g., change "20+ = 25%" to "20+ = 30%") without deploying new code.
Use Case 2: Free Gift with Purchase
Scenario: Spend $150, get Product X free automatically added to cart.
Legacy Script: Not directly possible. Scripts can discount existing items, not add new ones. Required theme hacks.
Function Implementation: Use the Cart Transform API. If cart total exceeds threshold and gift product isn't present, add it as a merged line with 100% discount.
Benefit: Native support. No theme hacks. Gift appears correctly in order.
Use Case 3: B2B Payment Gating
Scenario: B2B customers should only see "Net 30 Invoice" payment option. Hide credit cards for them.
Legacy Script: Check customer tags, delete other payment methods.
Function Implementation: Use Payment Customization API. Check if customer has "B2B" tag. Return hide operations for all payment methods except "Net 30 Invoice".
Benefit: Same logic, but now configurable. You could expose "B2B tag name" as a metafield, so different stores can use different tag conventions.
10. Step-by-Step Migration Plan
Here is the comprehensive migration process I follow with Plus merchants:
Phase 1: Discovery & Audit (1-2 Days)
- Export all active Scripts from the Script Editor
- Document what each script does in plain English
- Categorize: Discount (Line Item/Order), Shipping, Payment
- Identify interdependencies (e.g., "Script A and Script B both affect the same product line")
- Note any dynamic values (thresholds, percentages) that should become Metafields
Phase 2: Environment Setup (1 Day)
- Install Shopify CLI 3.x on your development machine
- Create a new App in Partner Dashboard (Development App is fine for single-store)
- Scaffold a Function project:
shopify app generate extension - Connect to your development store
- Run
shopify app devto verify the connection
Phase 3: Development (3-10 Days per Function)
- For each Script, create a corresponding Function of the appropriate type
- Write the input query (GraphQL) to fetch needed data (cart lines, customer tags, metafields)
- Implement the business logic in your chosen language
- Write unit tests with sample input payloads
- Test locally using
shopify app function run
Phase 4: Deployment & Activation (1 Day)
- Deploy the app:
shopify app deploy - In Shopify Admin, go to Settings > Apps > [Your App] > Install
- For Discount Functions: Create a new Discount using your Function as the type
- For Shipping/Payment Functions: Activate in the Checkout Settings
- Configure any Metafields exposed by your Function
Phase 5: Parallel Testing (1-2 Weeks)
- Run both old Scripts and new Functions simultaneously (if possible)
- Compare discounts/behavior on test orders
- Monitor error logs in Partner Dashboard
- Fix any discrepancies
Phase 6: Cutover & Decommission (1 Day)
- Disable old Scripts in the Script Editor
- Confirm Functions are handling all logic
- Document the new architecture for your team
- Celebrate!
11. Debugging and Testing Strategies
Local Testing
The Shopify CLI provides a shopify app function run command that lets you test your function locally with a JSON input file.
Run: cat input.json | shopify app function run
Output will be the JSON your function returns. Compare it to expected output.
Unit Testing with Jest (JavaScript)
Production Debugging
Function errors appear in the Partner Dashboard under your App > Extensions > [Function] > Logs. Use console.error() (JS) or eprintln!() (Rust) to log debug information.
12. Performance Benchmarks and Optimization
In production environments, I have measured the following execution times:
| Scenario | Legacy Script | JavaScript Function | Rust Function |
|---|---|---|---|
| Simple threshold discount | 15-25ms | 2-4ms | 0.5-1ms |
| Complex tiered discount (10 tiers) | 40-80ms | 5-10ms | 1-3ms |
| VIP + Product exclusions (50 items) | 60-120ms | 8-15ms | 2-5ms |
Optimization Tips:
- Minimize the data requested in your input query. Only fetch fields you actually use.
- Avoid deeply nested loops. Flatten logic where possible.
- For Rust: Use iterators instead of manual loops for better compiler optimization.
- For JavaScript: Avoid
JSON.parse()on large objects inside the function.
13. Common Migration Mistakes to Avoid
- Trying to Migrate All Scripts at Once: Start with one script. Get comfortable with the workflow. Then tackle the rest.
- Ignoring Discount Stacking: In Scripts, you manually controlled stacking. With Functions, Shopify's "Discount Combinations" settings manage this. Configure them correctly or discounts may stack unexpectedly.
- Hardcoding Values: The whole point of Functions is configurability. Use Metafields for thresholds, percentages, and product exclusions.
- Skipping Unit Tests: You finally have the ability to test. Use it. Discount bugs are expensive.
- Not Monitoring Logs: After deployment, watch the Partner Dashboard logs for the first few days. Edge cases will surface.
- Forgetting About B2B: If you have B2B catalogs or customer-specific pricing, ensure your Function queries handle company context.
14. Frequently Asked Questions (15+ FAQs)
Can I just convert my Ruby code to Wasm directly?
No. There is no direct transpiler from Shopify's Ruby Script API to the Functions API. You must rewrite the logic using the new Input/Output schema.
Do I need to know Rust?
No. Shopify fully supports JavaScript/TypeScript functions using the Javy engine. For 90% of use cases, JavaScript is fast enough and much easier to write.
What happens if I don't migrate?
Eventually, checkout.liquid goes away. If you rely on Scripts to hide shipping methods or discount items, those scripts will stop firing on the new Checkout Extensibility checkout. You lose functionality.
Can Functions make external API calls?
No. Functions run in a sandboxed Wasm environment with no network access. However, you can pre-fetch data using Metafields or store configuration in the App's backend and reference it via configuration.
How do I handle discount stacking?
Shopify provides "Discount Combinations" settings in the Admin. You can configure whether your Function-based discount stacks with other discounts (product, order, shipping). This is more flexible than manual Script logic.
Can I have multiple Functions in one App?
Yes. A single App can contain multiple Functions of different types. This is useful for organizing related logic (e.g., all discount logic in one App).
What are the execution limits for Functions?
Functions have an "instruction count" limit rather than a time limit. As of 2026, the limit is 11 million instructions. This is generous for typical logic.
Can I use npm packages in JavaScript Functions?
Limited. Functions are compiled to Wasm, so you can't use packages that rely on Node.js APIs (file system, network). Pure-logic packages (like lodash for utilities) may work if they don't exceed size limits.
How do I debug a Function in production?
Use console.error() statements. These logs appear in the Partner Dashboard under your App's Function logs. Include context (cart ID, customer ID) for easier correlation.
Is there a timeout for Functions?
There's no explicit timeout in seconds, but the instruction count limit effectively acts as one. If your function is too complex, it will error with "exceeded instruction limit."
Can Functions access customer metafields?
Yes. You can query customer metafields in your input GraphQL query and use them in your logic. This is powerful for personalized discounts.
What about existing Scripts during migration?
You can run Scripts and Functions in parallel temporarily. However, be careful about conflicts (e.g., both applying discounts to the same line). Plan your cutover carefully.
Do Functions work with Shopify POS?
Yes. Functions apply to all checkouts, including Shopify POS, as long as the checkout uses the Checkout Extensibility stack.
Can I revert to Scripts if Functions break?
Temporarily, yes. You can disable Functions and re-enable Scripts. But this is not a long-term solution since Scripts are deprecated.
How much does it cost to use Functions?
Functions are free to use. You need a Shopify Plus plan to access Checkout Extensibility features, but there's no additional charge for Functions themselves.
15. Conclusion and Next Steps
Migrating from Shopify Scripts to Shopify Functions is not optional for Plus merchants who want access to modern checkout features. The longer you wait, the more technical debt you accumulate and the more urgent the migration becomes.
Here is your action plan:
- Audit your current Scripts today. Document what they do.
- Set up a local development environment with Shopify CLI.
- Start with your simplest Script. Migrate it as a learning exercise.
- Write tests. You finally can. Don't skip this step.
- Deploy to a development store first. Test thoroughly.
- Roll out to production with parallel testing.
- Monitor logs for the first week. Fix edge cases.
Need Expert Help With Your Migration?
I specialize in high-risk Script-to-Function migrations for Plus merchants. I can audit your legacy Ruby scripts, design the new architecture, write the Functions, test them, and handle the cutover so you don't lose a single sale.
Book a Migration Audit