What Shopify Functions Actually Are (and Why They Replaced Scripts)
If you opened the Shopify admin this week and clicked into the old Script Editor, you saw the warning banner: Shopify Scripts stop executing on June 30, 2026, and as of April 15, 2026 you can no longer edit or publish new Scripts. The replacement is Shopify Functions, a WebAssembly-based system that runs custom backend logic inside checkout, cart, and fulfillment at production speed. For merchants moving off Ruby-based Scripts, and for new developers joining Shopify in 2026, Functions are the only path forward.
A Shopify Function is a small program that Shopify executes server-side, inside a sandboxed WebAssembly runtime, at a specific extension point such as "after the buyer adds something to the cart" or "when the discount is being calculated." You write the logic in Rust or JavaScript, compile it to Wasm, deploy it as part of a Shopify app, and it runs millions of times a day on Shopify's infrastructure. Unlike Scripts, which only ran on Shopify Plus, Functions work on every plan, which is why this matters even for small merchants who never had access to custom checkout logic before.
This guide is for the developer who has built a few Liquid themes, maybe a basic embedded app, and now needs to write their first Function. We will cover what Functions can customize, the language tradeoffs, how to scaffold one with the Shopify CLI, a real discount example, how to deploy and debug it, and the hard limits that push you into a full custom app instead. If you want to go deeper on the surrounding ecosystem, bookmark the shopify-development category on our blog.
The Six Things Functions Can Customize
Shopify Functions are scoped to specific extension points. You cannot write a Function that does "whatever you want" — each one plugs into a defined API surface with a defined input schema and output schema. As of April 2026, there are six production-ready Function APIs that cover the overwhelming majority of checkout customization requests.
| Function API | What it controls | Common use case |
|---|---|---|
| Discount | Product, order, and shipping discounts in a unified API | Buy 2 get 1 free, tiered bulk pricing, free shipping over $75 |
| Cart Transform | Add, remove, expand, or merge cart line items | Bundles, component unbundling, gift-with-purchase |
| Delivery Customization | Rename, reorder, or hide shipping options at checkout | Hide "Express" when the cart contains a hazmat item |
| Payment Customization | Rename, reorder, or hide payment methods | Hide Cash on Delivery for orders over $500 |
| Shipping Discount / Rates | Modify delivery fees returned to the buyer | Regional rate overrides, loyalty-based free shipping |
| Order Routing Location Rule | Pick which fulfillment location handles each line | Route by inventory, proximity, or SKU class |
A few of these used to be separate APIs. In 2025 Shopify folded the old Product Discount, Order Discount, and Shipping Discount APIs into a single unified Discount Function API — one function can now reduce a cart line, discount the order subtotal, and grant free shipping all in one deploy. Gadget's deep-dive on what Functions can do and how to build with them is a good companion read if you want the full surface area mapped out before picking your first project.
The mental model matters: Cart Transform answers "what does the cart look like," Discount answers "what price adjustments apply," and Delivery/Payment Customization answer "what options are shown." Shopify runs them in that order, so a Cart Transform that adds a bundle line item has already happened by the time your discount logic sees the cart. Mixing these up is the single most common source of bugs in your first Function.
Rust, JavaScript, and Why Everything Compiles to Wasm

Every Shopify Function ends up as a WebAssembly module. Shopify's runtime only executes Wasm — so the "language choice" is really a question of which language you prefer to compile from. The two officially supported paths in 2026 are Rust and JavaScript (with TypeScript as a superset of JavaScript).
- Rust → Wasm. Native compilation target. Smallest binaries, fastest execution, strongest type system. Shopify's own internal teams use Rust for performance-critical Functions.
- JavaScript / TypeScript → Wasm. Compiled via Javy, Shopify's JavaScript-to-Wasm toolchain. Familiar syntax, larger binary, roughly 3x slower than Rust at runtime.
- AssemblyScript → Wasm. Community-supported. TypeScript-flavored syntax that compiles more directly to Wasm. Popular before Javy matured, now a niche choice.
The speed gap sounds dramatic until you see the actual instruction budget. Shopify enforces an 11 million instruction limit per Function invocation — blow past it and Shopify rejects the output and falls back to default behavior. For 95% of Functions (a tiered discount, a basic bundle, hiding one shipping method) that limit is effectively unreachable in either language, which is why Capaxe Labs' 2025 Rust vs JavaScript benchmark recommends starting in JavaScript and only switching to Rust when profiling says you must.
When to pick Rust
- You are doing complex B2B pricing math with many line items and tiers
- You are calling external APIs via Network Access and need tight latency budgets
- Your team already writes Rust and wants compile-time safety guarantees
- You have hit the 11M instruction ceiling in a JavaScript prototype
When to pick JavaScript
- You are new to Functions and want to ship this week
- Your logic is simple: a discount threshold, a shipping rule, a cart rename
- You already use TypeScript in your app backend and want schema types to match
- You want the widest pool of developers who can maintain this later
If you are running B2B with deep pricing logic, the B2 Agency migration case study is worth a read — they started in TypeScript, hit scaling walls at 200+ line items, and migrated to Rust with intelligent caching. For most of us, that is a Year 2 problem, not a Day 1 problem.
Prerequisites Before You Write a Line of Code
You need four things installed and configured before shopify app generate extension will work. Skipping any of them leads to the vague "command not found" or "no partner organization" errors that eat an afternoon on your first try.
- Node.js 20.10+ and a package manager. pnpm, npm, or yarn all work. The CLI itself runs on Node.
- Shopify CLI 3.x or later. Install with
npm install -g @shopify/clior run viapnpm dlx @shopify/cliper project. Check withshopify version. - A Shopify Partner account and a development store. Sign up free at partners.shopify.com. You need this to create an app and run it against a real store.
- A Rust toolchain OR confidence with JavaScript. For Rust Functions you need
rustupand thewasm32-wasip1target. For JavaScript Functions, Javy is installed automatically by the CLI.
If you are using AI assistance, install the Shopify Dev MCP server now. Released April 2026, it gives Claude Code, Cursor, and similar tools direct access to Shopify's GraphQL schemas and current documentation. Instead of Claude guessing at an input query, it validates against the live schema. The Ask Phill team's walk-through of the Shopify AI Toolkit for Claude is the clearest beginner explainer I've seen of what it changes day to day.
Once the prerequisites are in place, authenticate the CLI once with shopify auth login and you are ready to scaffold.
Your First Function: Auto-Apply a 10% Discount on Orders Over $100

We will write a Discount Function in TypeScript that applies a 10% order discount when the cart subtotal is at least $100. This is the "hello world" of Shopify Functions — small enough to reason about, realistic enough to actually ship.
Step 1: Scaffold the app and the function
# Create the app shell (pick "Remix" when prompted)
shopify app init my-discount-app
cd my-discount-app
# Generate the function extension inside the app
shopify app generate extension
# Choose: Function > Discount
# Language: JavaScript (TypeScript)
# Name: threshold-discountThe CLI drops a folder at extensions/threshold-discount/ with three files that matter:
src/cart_lines_discounts_generate_run.ts— the discount logic entry pointsrc/cart_lines_discounts_generate_run.graphql— the input query you need to readshopify.extension.toml— extension config, targets, network access flags
Step 2: Write the input query
Open the .graphql file and replace the default with:
query RunInput {
cart {
cost {
subtotalAmount {
amount
}
}
}
}This tells Shopify exactly which cart data to pass your Function at runtime. The narrower the query, the cheaper each invocation — do not query fields you do not use, it counts against your instruction budget.
Step 3: Write the run target
Open cart_lines_discounts_generate_run.ts and replace the body:
import type { CartLinesDiscountsGenerateRunResult, RunInput } from "../generated/api";
export function cartLinesDiscountsGenerateRun(input: RunInput): CartLinesDiscountsGenerateRunResult {
const subtotal = parseFloat(input.cart.cost.subtotalAmount.amount);
if (subtotal < 100) {
return { operations: [] };
}
return {
operations: [
{
orderDiscountsAdd: {
candidates: [
{
message: "10% off orders over $100",
targets: [{ orderSubtotal: { excludedCartLineIds: [] } }],
value: { percentage: { value: 10 } },
},
],
selectionStrategy: "FIRST" as const,
},
},
],
};
}That is the entire Function. It reads the subtotal, checks a threshold, and returns either an empty operations array (no discount) or a single orderDiscountsAdd operation with a 10% percentage reduction. Shopify handles the math, the currency, and the display.
Step 4: Generate types
From the extension folder, run:
shopify app function typegenThis reads your GraphQL query and generates TypeScript types for RunInput and the result shape. Rerun it whenever you change the query.
For a more elaborate example — tiered discounts, metafield-driven thresholds, per-line-item targeting — the Shopify function-examples GitHub repo has production-grade reference code for every Function type. Clone it and read at least one example for whatever API you are about to build.
Deploy, Install, and Actually See It Run
Writing the code is the easy part. Getting it into a development store where you can test real cart behavior is where most beginners stall.
- Build the Function locally:
shopify app function buildcompiles your TypeScript to Wasm and confirms there are no schema errors. - Start the dev session:
shopify app devopens a tunnel and registers your app with the development store. Follow the prompted URL to install the app on your dev store. - Create a discount that uses the Function: In the Shopify admin, go to Discounts > Create discount > Your app's discount name. Give it a code or make it automatic. This is the step people forget — deploying a Function does nothing until a merchant creates a discount that references it.
- Add items to a cart and test: On the storefront, add $100+ of product. The 10% discount should appear on the cart and checkout page.
- Ship to production:
shopify app deploycreates a new app version with the Function baked in. Merchants install your app through the Shopify App Store or a custom distribution link, then create a discount from your Function.
A detail that trips people up: Functions deploy as part of an app version, not individually. Every time you change the Function code, you ship a new app version and any merchant currently using the old version keeps running the old code until they update. For your own store that is invisible; for a public app with hundreds of merchants, version management becomes its own discipline.
Debugging When It Does Not Work
Functions run inside a sandboxed Wasm runtime with no browser console and no attached debugger. The feedback loop is intentionally narrow. Shopify gives you three tools:
- `shopify app function run` — executes your Function locally against a sample input JSON and prints the output. Use this to iterate on logic without touching the store.
- `shopify app function replay` — replays a real invocation from your development store. The CLI captures recent runs; you pick one and re-run it locally with the exact input Shopify sent. This is the fastest way to reproduce a storefront bug.
- Partner Dashboard logs. Every Function invocation is logged with input, output, duration, and instruction count. If your Function silently stopped working in production, this is where you look first.
The five errors you will hit in week one
- "Output does not conform to schema." You returned a field Shopify does not expect, or a type Shopify cannot parse. Re-run
function typegenand use the generated types. - "Instruction count limit exceeded." Your logic is looping over too many cart lines or parsing too much JSON. Narrow your GraphQL query first; optimize algorithms second.
- "Function timed out." You are probably making a Network Access call without a tight timeout. Set a 500ms client-side budget.
- The Function deploys but no discount appears. You did not create a discount in the admin that points to this Function. See step 3 above.
- "No targets specified." Your
shopify.extension.tomlis missing the[[extensions.targeting]]block, or the target name does not match the exported function.
Once you have done this loop two or three times — scaffold, edit, build, run locally, replay, deploy — the rhythm gets very fast. Shopify's Partner Blog has a full walkthrough of the deploy cycle with screenshots of each admin step.
Common Mistakes (and How to Avoid Them)

| Mistake | What actually happens | Fix |
|---|---|---|
| Treating Functions like Scripts | Scripts ran per-checkout with a Ruby script per use case. Functions are shipped inside apps, versioned, and scoped to specific APIs | Plan the migration per feature, not script-by-script. Follow the Digital Applied migration guide for June 2026 for Script-to-Function mappings |
| Querying every cart field | Eats your instruction budget, slows checkout | Query only what your logic reads. Re-run typegen whenever the query changes |
| Forgetting to create the discount in admin | Function deploys, but nothing happens on the storefront | After shopify app dev, always go to admin > Discounts and create one that references the Function |
| Writing Rust on day one | Learning curve eats a week, and you probably do not need the perf | Start in JavaScript. Migrate to Rust only if profiling shows you must |
| Skipping shopify app function replay | You debug by staring at code and guessing what Shopify sent | Replay real invocations locally. It is the single best debug tool |
| Storing config in the Function code | Every config change is an app redeploy | Use metafields on the discount or shop object. Read them via the input query |
| Ignoring the 11M instruction limit | Production Function silently fails, discount stops applying | Monitor Partner Dashboard logs for instruction count trends. Alert at 80% of limit |
The migration mistake is the most expensive one. Merchants move their Scripts to Functions one-for-one, then discover the new unified Discount API wants a different mental model. The Badger Blue 2026 migration checklist breaks down each Script pattern and the closest Function equivalent, which saves you from re-discovering the mapping yourself.
Using Claude Code and the Dev MCP for AI-Assisted Development

The biggest practical change for beginner Function developers in 2026 is AI assistance that actually understands Shopify's schemas. Before the Shopify Dev MCP server shipped, asking Claude or ChatGPT to write a discount Function produced plausible-looking code that referenced renamed APIs, invented fields, or returned shapes that failed schema validation at build time.
The Dev MCP fixes that by giving the AI direct access to four things:
- Live GraphQL schemas for every Function API, so generated queries actually validate
- Current Shopify developer documentation pulled on demand, not from a training cutoff
- The Admin API schema for any app work that touches products, orders, or metafields
- CLI command execution for scaffolding, building, and deploying without leaving the editor
To hook it into Claude Code, install the @shopify/dev-mcp package locally and add it to your .mcp.json. The Fudge.ai 2026 setup guide walks through the exact config, including the auth flow for connecting to a specific Partner organization.
The workflow it enables: describe the discount rule in plain English, have Claude scaffold the Function using the real CLI commands, review the generated GraphQL query against the live schema, and build + deploy without copy-pasting between your terminal and a browser. If you already use Claude Code for other work, spending an hour wiring the Dev MCP up pays off within your first Function. Our category page on AI and emerging tech has more on where this tooling is heading.
When Functions Are Not Enough (Build a Full Custom App Instead)
Functions are powerful but deliberately narrow. Each API has a defined input and output shape, runs in a strict Wasm sandbox, and cannot call out to arbitrary services without Network Access (which itself is scoped and rate-limited). The limits exist to keep checkout fast and predictable. That is a feature, not a bug — but it means some custom requirements need a full app, not a Function.
You need a custom app instead of (or in addition to) a Function when:
- You need to modify checkout UI, not just the math. That is a Checkout UI extension — a separate system from Functions. See the payments-checkout category for customization patterns.
- You need persistent state or a background job. Functions are stateless per-invocation. Write the state in your app backend and read it from a metafield.
- You need to call external APIs on every cart change. Network Access has strict timeout budgets. If your pricing depends on a slow external ERP, cache aggressively and build the cache-warming in your app, not the Function.
- You need to respond to webhooks (order paid, inventory adjusted, etc.). Webhooks go to your app backend. Functions do not run on webhook events.
- You need multi-step buyer flows. Cart attributes, checkout UI, post-purchase pages — Functions touch none of those.
A realistic architecture for a serious merchant: a custom Remix app that handles admin UI, webhook processing, background jobs, metafield management, and external API calls — plus one or more Functions that the app installs, for the checkout-time decisions. The Function is the thin, fast, sandboxed layer; the app is everything else. If you are new to the custom app side, our primer on how to build a Shopify app from scratch is the best starting point, and the deeper patterns live in our shopify-development archive.
What to Build First (and Where to Go Next)

If you are reading this because you have a June 2026 Scripts deadline, pick your single highest-volume Script and migrate it first. That is usually a tiered discount or a free-shipping-over-threshold rule — both map cleanly to the unified Discount Function API. Ship that in a week, then tackle the rest.
If you are new to Shopify development and starting fresh, build the threshold discount Function from the example above in your development store, then follow it with a Cart Transform that bundles two products. Those two Functions together teach you 80% of what you need to know: input queries, operation schemas, deploy cycles, and admin-side discount configuration.
If you manage a B2B store with complex pricing, clone the Shopify/function-examples repo and read the B2B pricing examples before you write a single line. The patterns there — dimension-based pricing, customer-segment gates, metafield-driven rules — will save you a rewrite in six months.
One more resource: the Talk Shop community has an active thread of developers shipping Functions right now. If you hit a wall on instruction counts or a schema error nobody has seen before, post the error and the input query. Someone has almost certainly seen it and can unblock you in an hour. For deeper architecture reading across our whole library, start from our blog.
Quick recap of what matters: Functions replaced Scripts and run on every Shopify plan. They cover six specific APIs (discount, cart transform, delivery, payment, shipping rates, order routing). Start in JavaScript unless you have a specific reason for Rust. Scaffold with shopify app generate extension, build with function build, debug with function replay, and remember the discount-admin step or nothing will apply. Use the Dev MCP with Claude Code for AI-assisted scaffolding. And when the narrow Function sandbox is not enough, step up to a custom app.
What are you migrating first — a tiered discount, a shipping rule, or something stranger?

About Talk Shop
The Talk Shop team — insights from our community of Shopify developers, merchants, and experts.
