Most Shopify app subscriptions exist to solve one narrow problem: a specific discount rule, a shipping edge case, a payment method that should disappear for certain carts, a bundle that needs its own pricing logic. For years, the only way to get that behavior was installing a third-party app, paying a monthly fee, and accepting whatever script weight it added to your storefront. Shopify Functions change that trade-off for a meaningful slice of these use cases — running your custom logic on Shopify's own infrastructure instead of a bolted-on app.
This is not a claim that Functions replace every app you run. Marketing pop-ups, review collection, email automation, and analytics still live outside this model. But for checkout, discount, shipping, payment, and cart logic — the categories where apps often cost the most and slow you down the most — Functions are frequently the better long-term architecture. This guide covers what Functions actually are, where they beat apps, example logic patterns, real limitations, and a practical migration plan.
If your current app stack is already dragging down site speed, pair this guide with our Shopify speed optimization guide. For a broader view of which apps are worth keeping, see our essential Shopify apps guide.
What are Shopify Functions?
Shopify Functions are small pieces of backend logic — written in JavaScript or Rust and compiled to WebAssembly — that run directly on Shopify's servers at checkout, cart, and discount decision points. Unlike apps, they do not inject client-side scripts into your storefront, which makes them faster and more reliable for logic like custom discounts, shipping rules, payment method visibility, and cart transformations.
What Shopify Functions Actually Are
A Shopify app, in the traditional sense, is an external service that talks to your store through APIs and often injects JavaScript into your theme or checkout to change behavior on the client side. That script has to load, execute, and sometimes call back to an external server before it can act — every one of those steps adds latency and a potential point of failure.
A Shopify Function is different: it is a small, sandboxed piece of code that Shopify itself runs, server-side, at a specific extension point — a discount calculation, a shipping rate list, a payment method list, or a cart line transformation. It does not run in the customer's browser. It does not need to be loaded as a script tag. Shopify's checkout calls the function, gets a structured response, and applies it. This is the same architectural pattern used by serverless functions in modern web development, applied specifically to commerce logic Shopify controls.
| Aspect | Traditional app (client-side) | Shopify Function |
|---|---|---|
| Where it runs | Customer browser / external server | Shopify's infrastructure, server-side |
| Storefront script weight | Adds JS to theme or checkout | None — no client-side script required |
| Latency profile | Depends on network + app server response time | Runs inline with Shopify's own request handling |
| Extension points | Broad (theme, admin, anywhere via API) | Specific: discounts, shipping, payment, cart, delivery |
| Typical use case | Reviews, popups, loyalty, broad customization | Pricing logic, checkout rules, cart transformations |
The core idea in one line
Functions move decision logic that used to require a client-side app script into Shopify's own checkout and cart processing — faster, more reliable, and billed differently (development time and hosting, not necessarily a recurring per-order app fee).
Discount Functions: Replacing Complex Discount Apps
Discount apps are one of the most common recurring subscriptions on growing Shopify stores — tiered discounts, buy-one-get-one logic, mix-and-match pricing, and customer-segment-specific offers. Discount Functions let you write this logic yourself and run it natively at checkout, without a third-party discount engine intercepting the cart.
A discount function receives the cart contents and returns instructions for what discount to apply, to which lines, and why. The logic is entirely your own — which means you are not constrained by whatever rule structures a discount app's UI happens to support.
// Pseudocode: Discount Function logic for "Buy 2, get 1 free" on a specific collection
function run(input) {
const eligibleLines = input.cart.lines.filter(
line => line.merchandise.product.inCollection('bundle-eligible')
);
if (totalQuantity(eligibleLines) < 3) {
return noDiscount();
}
// Sort by price ascending, discount the cheapest qualifying unit
const cheapestLine = eligibleLines.sort(byUnitPriceAscending)[0];
return {
discounts: [{
targets: [{ productVariant: { id: cheapestLine.merchandise.id } }],
value: { percentage: { value: 100 } },
message: 'Buy 2, get 1 free'
}]
};
}The exact API surface (Shopify's discount function input/output schema) is more structured than this pseudocode, but the logic pattern holds: inspect the cart, decide what qualifies, return a discount instruction. Once deployed, this runs on every checkout without a script loading in the customer's browser and without a monthly per-order fee to a discount app vendor.
Shipping Customization Functions
Shipping apps commonly solve two problems: hiding certain shipping rates based on cart contents (no expedited shipping for hazardous or oversized items), and renaming or reordering rates for clarity (showing "Free Shipping" instead of a carrier-generated rate name). Shipping Functions handle both without a third-party app evaluating the cart mid-checkout.
// Pseudocode: Shipping Function to hide express shipping for oversized items
function run(input) {
const hasOversizedItem = input.cart.lines.some(
line => line.merchandise.product.hasTag('oversized')
);
if (!hasOversizedItem) {
return { operations: [] }; // no changes
}
const expressRate = input.deliveryGroups
.flatMap(g => g.deliveryOptions)
.find(option => option.title.includes('Express'));
if (!expressRate) {
return { operations: [] };
}
return {
operations: [{ hide: { deliveryOptionHandle: expressRate.handle } }]
};
}This kind of conditional shipping logic used to require a shipping-rules app charging monthly for functionality that, once written as a Function, has no per-order dependency on an external vendor's uptime.
Payment Customization Functions
Payment Customization Functions control which payment methods are shown at checkout based on cart or customer conditions — hiding cash on delivery above a certain order value, restricting a buy-now-pay-later option for B2B accounts, or showing manual payment methods only to approved wholesale customers.
// Pseudocode: Payment Function hiding a payment method above a cart value threshold
function run(input) {
const cartTotal = input.cart.cost.totalAmount.amount;
if (cartTotal <= 500) {
return { operations: [] };
}
const codMethod = input.paymentMethods.find(
m => m.name === 'Cash on Delivery'
);
if (!codMethod) {
return { operations: [] };
}
return {
operations: [{ hide: { paymentMethodId: codMethod.id } }]
};
}This is a common ask for merchants dealing with fraud risk on high-value cash-on-delivery orders, and previously required either a checkout app (Plus-only territory in many cases) or manual order review after the fact. A Function applies the rule before the order is even placed.
Cart Transform Functions: Bundles Done Right
Bundle apps are notorious for creating messy line-item structures — separate SKUs that do not clearly represent the bundle in reporting, inventory, or the order confirmation. Cart Transform Functions let you programmatically combine or modify cart lines so a bundle appears and behaves as a single coherent unit, with pricing and inventory logic you control directly.
// Pseudocode: Cart Transform Function merging 3 components into a fixed-price bundle
function run(input) {
const bundleComponents = input.cart.lines.filter(
line => line.merchandise.product.hasTag('starter-kit-component')
);
if (bundleComponents.length !== 3) {
return { operations: [] };
}
return {
operations: [{
merge: {
cartLines: bundleComponents.map(l => ({ cartLineId: l.id, quantity: 1 })),
parentVariantId: 'gid://shopify/ProductVariant/STARTER-KIT-BUNDLE',
price: { fixed: { amount: '49.99', currencyCode: 'USD' } }
}
}]
};
}This replaces bundle apps that rely on client-side cart manipulation or duplicate "bundle SKU" product records maintained separately from the components — a common source of inventory sync errors. The Function handles the transformation natively, at the cart level, on Shopify's own systems.
Delivery Customization and Order Routing (Briefly)
Two additional extension points are worth knowing even if they apply to fewer merchants: Delivery Customization Functions (controlling pickup point or local delivery option generation) and Order Routing/Fulfillment Constraint Functions (deciding which warehouse or location should fulfill an order based on inventory, proximity, or business rules). Multi-location merchants using apps purely to route orders to the correct fulfillment center are a common candidate for replacing that logic with a Function once volume justifies the development investment.
Why Functions Beat Apps: The Performance and Reliability Case
The case for Functions over apps in these categories comes down to three things: where the code runs, how much it costs to keep running, and how much control you have over the exact logic.
No storefront script weight
Discount, shipping, payment, and cart apps that operate client-side often need a script running in the storefront or checkout to evaluate conditions and apply changes. Functions eliminate that script entirely for the behaviors they replace — one less third-party script competing for main-thread time on your product and checkout pages.
No external network dependency at the moment of decision
A client-side app frequently calls out to its own servers to decide what discount or rate to apply. If that server is slow or briefly down, checkout behavior degrades or fails. A Function runs inside Shopify's own request handling — no separate network hop to a third-party service is required to evaluate the logic.
Versioned, testable, and fully owned
Functions are deployed through Shopify CLI like any other code artifact — versioned, testable in a development store, and rolled back like normal software. You are not dependent on an app vendor's roadmap to fix a bug or add a rule variant; you own the logic outright.
Where the performance win is most visible
High-traffic checkout and cart interactions are exactly where app-injected scripts do the most damage to conversion-sensitive load times. Replacing even two or three checkout-adjacent apps with Functions often produces a measurable, direct improvement in checkout responsiveness.
If checkout and cart performance is already a known weak point for your store, read this alongside our checkout optimization service overview — speed and Functions adoption are complementary workstreams, not separate projects.
The Limitations: What Functions Cannot Do
Functions are not a universal app replacement, and treating them as one leads to wasted development effort. Understand the boundaries before committing engineering time.
- No visual UI on the storefront — Functions return data and decisions, not rendered interfaces; you still need theme app extensions or admin UI extensions for merchant- or customer-facing screens
- Development resources required — Functions are written in JavaScript or Rust and deployed via Shopify CLI; there is no no-code builder for custom logic
- Not a fit for marketing, content, or analytics apps — popups, reviews, loyalty widgets, and tracking pixels are outside the Functions extension model entirely
- Some extension points require Shopify Plus — several checkout-related Function types are available only on Plus, which matters for smaller merchants evaluating the migration
- Testing and edge-case handling is your responsibility — a buggy discount app is the vendor's problem to fix; a buggy custom Function is yours
Do not underestimate the build cost
A Function that replaces a $30/month discount app is not automatically cheaper if it takes a developer two weeks to build, test, and maintain. Run the cost-benefit math per use case, not as a blanket "Functions are always better" assumption.
Cost Modeling: App Subscription vs. Build-and-Maintain
The financial case for Functions is not automatic. A discount app charging $49 per month costs $588 a year — cheap compared to even a few days of developer time to build, test, and maintain an equivalent Function. The math changes once you are running several overlapping apps solving related problems, or once an app's pricing scales with order volume or GMV rather than a flat fee.
Where the economics favor a Function
- Apps priced on a percentage of revenue or order volume, where cost grows faster than a one-time build investment
- Multiple apps solving overlapping problems that a single, well-designed Function could consolidate
- Logic stable enough that you are not paying for frequent app-vendor configuration changes
- Existing in-house or contracted development capacity, so the marginal cost of building is lower than hiring specifically for it
Where the economics favor keeping the app
A low, flat monthly fee for a stable, well-supported app is often the cheaper option in pure dollar terms, especially for merchants without existing development resources. The decision is not purely about performance — it is about whether the ongoing maintenance burden of owned code is worth taking on internally versus outsourcing it to an app vendor's support team.
Functions and Shopify Plus: What to Know Before You Build
Several of the most valuable Function extension points for checkout customization — particularly around payment methods and certain delivery customizations — are available only on Shopify Plus. Discount Functions and some Cart Transform use cases are more broadly available, but merchants evaluating a Functions-first strategy should confirm extension-point availability against their current plan before investing development time in a build that cannot ship.
This is a separate question from whether Plus is worth it generally — see our <a href="./blog/shopify-plus-vs-shopify-advanced-which-plan-is-right">Shopify Plus vs Shopify Advanced comparison</a> for that broader decision. For this guide, the practical takeaway is narrower: confirm which Function types your plan unlocks as the very first step of scoping any migration project, not after development is underway.
When Functions Beat Apps (And When They Do Not)
| Situation | Better fit |
|---|---|
| Standard discount rules an app's UI already covers well | Keep the app — low marginal benefit from custom code |
| Highly specific discount/pricing logic no app supports cleanly | Function — direct control without workaround stacking |
| High checkout traffic where app script latency is measurable | Function — removes client-side dependency at the highest-value moment |
| Marketing, reviews, loyalty, or analytics tooling | App — outside the Functions extension model |
| Multi-warehouse fulfillment routing with unique business rules | Function — once volume justifies the development investment |
| Early-stage store without in-house or contracted dev resources | App — Functions require engineering capacity apps do not |
Three Realistic Scenarios
Scenario 1: A supplements brand with tiered subscription discounts
A supplements merchant was paying for a discount app purely to apply escalating discounts based on subscription frequency (10% for monthly, 15% for every-two-months, 20% for quarterly) combined with a separate volume discount for multi-product bundles. The two apps occasionally conflicted, double-applying discounts on edge-case carts. A single Discount Function consolidated both rules into one deterministic piece of logic, eliminating the conflict entirely and removing one app subscription.
Scenario 2: A furniture retailer with freight-sensitive shipping rules
A home goods store needed to hide standard parcel shipping whenever a cart contained freight-only items, and rename the remaining freight rate with delivery-window messaging. The shipping app handling this added a visible delay on the shipping step as it queried an external service for every checkout. A Shipping Function moved the same logic inline, removing the external call and the associated delay.
Scenario 3: A multi-brand accessories store with a messy bundle SKU problem
A bundle app was creating a separate "bundle" SKU for every combination a merchandising team wanted to sell, which meant inventory had to be manually synced between component products and the bundle SKU. A Cart Transform Function merged component line items into a bundle presentation at the cart level without a separate inventory record, removing an entire category of stock discrepancies caused by manual bundle SKU management.
None of these scenarios required replacing every app the merchant used — in each case, one or two specific, well-understood pieces of logic moved from an app to a Function, while broader marketing and lifecycle apps stayed exactly where they were.
A Practical Migration Plan From Apps to Functions
Moving from an app-based approach to Functions works best as a staged, low-risk process rather than a one-time rip-and-replace of your entire app stack.
Step 1: Audit your current app-driven logic
List every app touching discounts, shipping, payment methods, and cart contents. For each, note the monthly cost, the exact rule logic it enforces, and how business-critical it is. This audit alone often reveals redundant apps solving overlapping problems.
Step 2: Identify Functions-replaceable candidates
Prioritize apps where the logic is well-defined, stable, and does not need a merchant-facing configuration UI that changes often. A fixed bundle pricing rule is a strong candidate; a marketing team's frequently-changing promotional discount calendar may be better served by keeping a flexible discount app.
Step 3: Build and test in a development store
Develop the Function against Shopify CLI in a development store, replicating the exact cart, discount, or shipping scenarios the app currently handles. Test edge cases explicitly — empty carts, mixed eligible/ineligible line items, currency variations — since there is no vendor safety net catching a bug in your own code.
Step 4: Run in parallel before cutting over
Where possible, validate the Function's output against the existing app's behavior on live-like data before fully disabling the app. This catches logic mismatches before customers see them at checkout.
Step 5: Cut over and monitor
Disable the app, deploy the Function to production, and monitor checkout completion rate, discount application accuracy, and support tickets closely for the first one to two weeks. Keep the ability to quickly revert to the app if something behaves unexpectedly at scale.
- Full audit of discount, shipping, payment, and cart apps with cost and logic documented
- Candidates prioritized by stability of rules and expected performance or cost benefit
- Function built and tested against real cart/discount scenarios in a development store
- Parallel validation against existing app behavior before cutover
- Monitoring plan and rollback path defined before disabling any app in production
Key Takeaways
Key takeaways
- Shopify Functions run server-side on Shopify's own infrastructure, replacing client-side app scripts for discount, shipping, payment, and cart logic.
- Discount, Shipping, Payment Customization, and Cart Transform Functions cover the categories where apps most commonly cost the most and slow checkout down.
- The performance and reliability case is strongest at checkout, where app script latency and external network dependencies directly affect conversion.
- Functions require real development resources and are not a fit for marketing, analytics, or UI-heavy app categories.
- Migrate deliberately: audit current apps, pick stable high-value candidates, build and test in a development store, then cut over with monitoring in place.
- Not every app should become a Function — weigh build and maintenance cost against the app subscription it would replace before committing.
Not sure which of your Shopify apps are good Functions candidates? Book a free 30-minute audit or start with our free Shopify audit tool to see where app costs and checkout performance intersect on your store.
Considering a move from apps to Shopify Functions?
CROVEX audits your current app stack, identifies strong Functions candidates, and builds a migration plan that protects checkout stability while cutting recurring app costs.
Book Free Shopify AuditFrequently Asked Questions
Shopify Functions are small pieces of backend logic — written in JavaScript or Rust and compiled to WebAssembly — that run directly on Shopify's servers at checkout, cart, and discount decision points. They replace client-side app scripts for logic like custom discounts, shipping rules, payment method visibility, and cart transformations.
No. Functions are strong replacements for discount, shipping, payment customization, and cart transform logic, but marketing, reviews, loyalty, and analytics apps fall outside the Functions extension model entirely and still require traditional apps.
Yes. Functions are written in JavaScript or Rust and deployed through Shopify CLI. There is no no-code builder, so you need in-house or contracted development capacity to build, test, and maintain them.
For the extension points they cover, generally yes. Functions run server-side within Shopify's own checkout processing, avoiding the client-side script loading and external network calls that many discount, shipping, and payment apps require.
Some do. Several checkout-related Function types, particularly around payment customization and certain delivery scenarios, are available only on Shopify Plus. Discount Functions and some Cart Transform use cases are more broadly available. Confirm plan-level availability before scoping a build.
Cart Transform Functions let you programmatically merge, adjust, or replace cart line items — commonly used to build bundles that behave as a single coherent unit with custom pricing, instead of relying on separate bundle SKUs managed manually alongside component inventory.