Loading tutorials…
Loading tutorials…
Shopify Functions are the official Scripts replacement. They run server-side at checkout, written in JavaScript or Rust, deployed via CLI. Most Plus brands need 3-7 Functions to replicate their old Scripts. Here's how to build the first one.
Who this is forShopify Plus brands migrating from Scripts to Functions, or building net-new discount/shipping/payment customizations. Requires basic developer comfort — Functions are code, not no-code.
What you'll need
Step 1
Shopify Functions come in 5 types: Product Discount, Order Discount, Shipping Discount, Delivery Customization, Payment Customization. Each replaces a different category of Script.
Product Discount: applies discount to specific products/variants based on cart contents, customer, or metafield rules. Replaces line-item-level discount Scripts.
Order Discount: applies discount to the cart total based on cart-level conditions (e.g., 'spend $100, get 10% off'). Replaces order-total discount Scripts.
Shipping Discount: discounts a specific shipping rate (e.g., 'free shipping for orders over $50'). Replaces shipping-discount Scripts.
Delivery Customization: shows/hides/renames shipping rates at checkout based on cart, customer, or address rules. Replaces 'hide express shipping for low-AOV carts' Scripts.
Payment Customization: shows/hides/renames payment methods at checkout based on cart, customer, or address rules. Replaces 'hide PayPal for B2B customers' Scripts.
If you have a Script doing multiple things (e.g., 'discount + hide shipping'), split it into multiple Functions — Functions are single-purpose.
Step 2
Shopify CLI is the only deploy tool. Install it, authenticate, and scaffold an extension app.
Install Node.js 18 or higher: `node --version` should show v18+. If not, install from nodejs.org.
Install Shopify CLI globally: `npm install -g @shopify/cli @shopify/theme`. Confirm: `shopify version`.
Authenticate: `shopify login --store=your-sandbox-store.myshopify.com`. Opens a browser OAuth flow.
Initialize an app (only once per project): `shopify app init`. Choose 'Build a Shopify app' → name it 'checkout-functions'. This creates a local project.
Inside the project, generate the first Function: `shopify app generate extension`. Choose 'Function' → pick the type (Product Discount, Order Discount, etc.) → language (JavaScript is simpler to start; Rust is faster).
The CLI scaffolds a Function folder with `src/run.ts` (or `src/lib.rs` for Rust), `shopify.extension.toml` (config), and tests.
Step 3
Open `src/run.ts`. The function receives a GraphQL input (cart, customer, shipping rates) and returns a structured output.
Look at the scaffolded `run` function. It receives an `input` object (typed via GraphQL schema) and returns a `FunctionResult`.
Example: Order Discount Function that gives 10% off if cart total > $100. Inside `run`, check `input.cart.cost.totalAmount.amount`. If > 100, return `{ discounts: [{ targets: [{ orderSubtotal: { excludedVariantIds: [] } }], value: { percentage: { value: '10.0' } } }] }`.
If conditions aren't met, return `{ discounts: [] }` — no discount applied.
Update the GraphQL `input.query` (in `src/run.graphql`) to fetch only the fields you need. Pulling unused fields costs execution time and may exceed input-size limits.
Test locally: `shopify app function run --input=...` simulates the Function with a mock input. Useful for fast iteration without deploying.
Step 4
Each Function has a `shopify.extension.toml` file. Configure name, description, target, and input query.
Open `shopify.extension.toml`. Set: `name`, `type` (`product_discounts`, `order_discounts`, etc.), and `targeting.target` (which extension point in checkout).
Set `input.query` to a `.graphql` file with the query you wrote. The CLI validates the query against the Function input schema.
Rate limits: Functions must execute in under ~5ms typical, with input/output size under 100KB. For complex logic on large carts, switch to Rust Functions (10x faster).
Optional: configure a `metafields` array to allow merchants to customize Function behavior via metafields without redeploying. Common pattern: 'discount percentage' as a metafield read by the Function.
Save. The CLI auto-validates the config on deploy.
Step 5
Deploy the Function via CLI, then activate it in Shopify Admin → Discounts (or Shipping / Payments).
Deploy: `shopify app deploy`. The CLI bundles, validates, and uploads the Function. First deploy may take 2-3 minutes; subsequent deploys are fast.
Open the sandbox admin → Discounts (for Discount Functions) → Add → Custom → select your Function from the list.
Configure the discount: title, eligibility (all customers or specific segments), combinations (stack with other discounts? exclusive?), active dates.
Save. The discount is live in the sandbox.
Run a real test checkout: add products to cart that trigger your Function (e.g., cart over $100), proceed to checkout, verify the discount applies.
If it doesn't apply, debug: admin → Apps → your-function-app → Logs. Function errors and execution metrics show here.
Step 6
Once tested in sandbox, deploy to your production Plus store. The Function code is the same; the activation happens per-store.
Switch the Shopify CLI to your production store: `shopify login --store=your-prod-store.myshopify.com`.
Deploy again: `shopify app deploy`. Same code, different store.
Open production admin → Discounts (or Shipping / Payments) → Add → Custom → select your Function.
Configure with production-appropriate settings (date range, customer eligibility, etc.). Test by running a test order in incognito with a real card.
Monitor execution: admin → Apps → your-function-app → Logs. Watch error rate and execution time for the first 24-48 hours.
Document the Function: what it does, when it triggers, what the math is, where the code lives. Save in your team wiki.
Step 7
Functions are code. They belong in git, with CI/CD deploying changes on merge. Set up the repo properly to avoid future drift.
Create a git repo for the Function app project (or include it in your main monorepo). Commit the entire project: `src/`, `shopify.extension.toml`, `package.json`.
Set up GitHub Actions (or your CI of choice). Workflow: on push to `main`, run `shopify app deploy` with a Shopify CLI token.
Generate a CLI token for CI: Shopify Partners → Apps → your app → API credentials. Save as a GitHub Actions secret.
Add a deployment check: before deploy, run `shopify app function run` with a sample input to confirm the Function doesn't crash on basic cases.
Document the deploy process: 'Edit code → commit → push → CI deploys → activate in admin if it's a new Function.' Save in README.
Plan for the next Function: each one follows this pattern, so reuse the same app project. Don't create a new app per Function — they live as sibling extensions.
Common mistakes
Skipping local testing and deploying straight to prod
What goes wrong: Untested Function logic deploys to prod. First customer hits checkout, Function crashes or applies wrong discount. Best case: discount doesn't apply (revenue impact = ad spend wasted). Worst case: wrong discount applies, every order is at 90% off until someone notices.
How to avoid: Always test locally with `shopify app function run` against sample inputs. Then deploy to sandbox. Then run a real test checkout in sandbox. Then deploy to prod.
Exceeding input-size or execution-time limits
What goes wrong: Function input over 100KB or execution over 5ms (JavaScript) fails silently. Function 'doesn't apply' for high-volume carts. Discovered usually when a B2B customer with a 50-line order complains the bulk discount didn't apply.
How to avoid: Test with worst-case inputs (large carts, complex pricing). Trim the GraphQL query to fetch only necessary fields. For heavy logic, switch from JavaScript to Rust Functions.
Building one mega-Function instead of multiple single-purpose Functions
What goes wrong: Trying to recreate a 200-line Script as one Function violates the type system (each Function type does one thing). Devs hack around it with workarounds that break on Shopify updates. Bug rate goes up 5-10x.
How to avoid: Split logic across Functions by type. One Order Discount Function, one Shipping Customization Function, etc. They can read shared metafields if needed.
Not version-controlling the Function code
What goes wrong: Function code lives only on the developer's laptop. They quit, leave the company, or break their machine. The team can't modify the Function or even understand what it does. The only path forward is rewriting from scratch.
How to avoid: Put the entire Shopify app project (including all Functions) in git from day one. Set up CI/CD for deploys. Document in README.
Forgetting to set discount combinations correctly
What goes wrong: Function-driven discount conflicts with other discounts (Shopify-native discount codes, automatic discounts). Stacking is misconfigured. Customer applies a code, expects 30% off, gets 10% off — confusion + abandoned cart.
How to avoid: When activating the Function discount in admin, set "Combinations" thoughtfully: which other discount types can stack with this one? Test with a real cart that has multiple eligible discounts.
Not monitoring Function logs in production
What goes wrong: Function silently throws errors for edge-case inputs (e.g., gift card-only carts, unusual currencies). 5-10% of checkouts hit the silent failure. Revenue drops slowly, attributable to nothing obvious.
How to avoid: Check Function logs weekly: admin → Apps → your-function-app → Logs. Watch error rate. Set up an alert if error rate > 1% over a 24h window.
Recap
Done — what's next
How to set up Shopify Plus checkout extensibility
Read the next tutorial
Hand it off
Functions are the highest-leverage skill on the Plus stack and the rarest. A Shopify dev who can ship Functions reliably is worth their weight. At $14-16/hr, a vetted specialist can migrate 5-7 Scripts to Functions in 1-2 weeks — typically $1,200-2,400 total — plus ongoing maintenance at $200-400/mo.
See specialist rates
JavaScript is enough for 80% of Functions. It's simpler to write, debug, and maintain. Switch to Rust only if you're hitting execution-time limits (typically with large B2B carts or complex pricing rules). Most agencies write JavaScript by default.
Some Function types are now available on standard Shopify (Product Discount, Order Discount, Shipping Discount). Delivery Customization and Payment Customization remain Plus-only. Check the latest Shopify dev docs — coverage is expanding.
Inventory each Script, decide the equivalent Function type(s), and rewrite. Most Scripts split into 2-3 Functions because Functions are single-purpose. Plan 1-3 days per non-trivial Script. See the dedicated 'Troubleshoot Shopify Plus Script migration' tutorial for the full migration playbook.
Shopify Admin → Apps → your Function app → Logs. Shows execution count, errors, execution time, and input/output samples. Check weekly for the first month after deploy.
Limited. Functions run in a sandboxed runtime without general network access. There's a separate 'Network Access' feature in preview that allows specific outbound calls, but most Functions are pure logic over the cart input. For external API calls, use a Shopify Flow workflow or a webhook-driven backend instead.
Shopify Plus
On standard Shopify, checkout extensibility is mostly settings. On Plus, it's UI extensions, branding APIs, and Functions — code-deployed, version-controlled, infinitely flexible. The wrong setup is also infinitely expensive.
Shopify Plus
Most Plus brands migrated Scripts to Functions in a rush before the deprecation deadline. Now subtle bugs are surfacing: discounts not stacking, shipping rates wrong on edge cases, Functions silently failing. Here's the diagnostic sequence.
Shopify Plus
Shopify Flow is the free Plus-included automation tool. Most brands install it, build one workflow, then forget about it. The brands that get value out of Flow build 10-20 workflows that run silently in the background.
Shopify Plus
Shopify B2B (formerly the Wholesale Channel) is now native on Plus. Companies, price lists, net-30 terms, and customer-specific catalogs all work — but most brands set up Companies wrong and end up rebuilding 3 months later.
Shopify
Shopify killed checkout.liquid in August 2025. If you haven't migrated, your old pixels and order-confirmation customizations are silently broken. Here's the full checkout extensibility migration — pixels, CAPI, and post-purchase logic.