The framing “Shopify app vs Shopify Function” is almost always the wrong question. It isn’t either/or. A Shopify Function is backend logic that runs inside Shopify at checkout time — it applies a discount, validates the cart, or customizes payment and delivery. An app is the container around it: admin UI, configuration, storefront elements, APIs and integrations. Functions are built and deployed as part of an app, so they don’t compete — they compose. The real decision isn’t which one to pick; it’s what belongs in the Function and what belongs in the rest of the app. Get that boundary right and your architecture is clean, enforced, and cheap to maintain. Get it wrong and offers leak revenue at checkout.
First, clear up the framing
You cannot ship a Function on its own. A Function has no admin screen, no storefront widget, no settings page — it’s a small unit of server-side logic that plugs into an extension point Shopify has opened up. To exist on a store, it has to be packaged and deployed inside a Shopify app, even a custom app built for a single store.
So “app vs Function” is really “app and Function.” The useful mental model: the app is the house, the Function is the plumbing. The house gives you rooms, doors and a control panel; the plumbing is what actually has to work correctly and can’t be faked. Most serious builds need both — a thin custom app that carries one or two Functions plus a little configuration UI. For a deeper primer on the Function half, see what are Shopify Functions.
What a Function is and does
A Function runs server-side, inside Shopify’s own checkout calculation. Because of that, its result is authoritative — a shopper can’t edit the page, disable JavaScript, or replay a request to bypass it. That’s the entire reason Functions exist. The main extension points cover the moments where money and eligibility are decided:
- Discounts — product, order and shipping discounts with logic the native discount UI can’t express: tiered “buy more, save more,” bundle pricing, gift-with-purchase thresholds. See custom Shopify discount logic.
- Cart and checkout validation — block or alter checkout based on your rules, e.g. a minimum order value enforced at checkout rather than merely displayed.
- Payment customization — hide, rename or reorder payment methods based on cart contents, address or customer.
- Delivery customization — hide, rename or reorder shipping options under the same kinds of rules.
The common thread: these are backend decisions, not visual tweaks. If a merchant can ask “can a customer cheat this?”, the honest answer for anything running only in the browser is often “yes” — and the honest answer with a Function is “no.”
What the app provides
Everything a Function can’t do lives in the app around it:
- Admin UI — the settings screens a merchant uses to configure the behavior (built with Shopify’s app framework and admin components).
- Configuration and data — storing the merchant’s choices (thresholds, product selections, copy) and feeding them into the Function as metafields or configuration.
- Storefront UI — theme app blocks, widgets and progress bars that show the offer on the product page or cart. This is marketing; it is not enforcement.
- APIs and integrations — reading and writing store data, syncing with external systems, webhooks, background jobs.
- Permissions — the access scopes the merchant grants on install, which the Function inherits.
None of that runs at the checkout-calculation layer, and none of it needs to. It’s the connective tissue that makes the Function usable and configurable.
Responsibilities, mapped
Here’s the split that actually drives the architecture decision. For each responsibility, one side owns it:
| Responsibility | Shopify Function | The rest of the app |
|---|---|---|
| Admin UI / settings screens | — | ✅ App admin |
| Merchant configuration & stored data | Reads it | ✅ Owns & stores it |
| Storefront UI (widgets, progress bars) | — | ✅ Theme app block |
| Backend logic (pricing, eligibility) | ✅ Function | — |
| Checkout enforcement (must-hold rules) | ✅ Function | — |
| APIs / external integrations | — | ✅ App backend |
| Permissions / access scopes | Inherits | ✅ App requests |
| Deployment | Deployed with the app | ✅ App carries it |
| Maintenance & updates | Versioned with the app | ✅ App release cycle |
Read the table as a single rule: anything that must be correct at checkout is the Function’s job; everything else is the app’s.
How to decide what goes in a Function
Use one test — the “must hold at checkout” test. Ask: if the shopper edits the page, blocks scripts, or the widget fails to load, does this rule still have to be true?
- Yes → it belongs in a Function. Pricing rules, discount eligibility, minimum-order gates, payment/delivery restrictions, enforced bundles.
- No → it belongs in the app’s storefront or admin layer. Displaying an offer, animating a progress bar, showing a settings screen, sending data to an analytics tool.
That single question resolves most architecture debates. A “buy 3, get 1 free” badge on the product page is app UI; the fact that the fourth unit is actually free when the customer pays is a Function.
When a plain app (no Function) is enough
Plenty of good apps have no Function at all. If your requirement is entirely storefront or admin — a section that displays information, a tool that syncs products to a feed, a dashboard, an integration with an email platform — you don’t need checkout-time logic. Adding a Function there would be dead weight. Reach for a Function only when the “must hold at checkout” test comes back yes.
When custom app + Function beats an off-the-shelf app
Sometimes the answer is neither a plain app nor a bespoke build — it’s an existing app from the store. A custom app with a Function is warranted when:
- Your discount, bundle or checkout rule is specific enough that no off-the-shelf app expresses it, or apps that claim to do it run in JavaScript and break at checkout.
- You need the logic enforced, not advertised — the difference between an offer that works and one shoppers can game.
- You’re stacking multiple rules that a generic app can’t coordinate, or you need it to integrate with your own systems.
If an existing app already does the job and holds at checkout, use it. Custom is worth it precisely when the requirement is unusual and checkout-critical — which is exactly the territory a Function owns.
Deployment and maintenance
This is where “app and Function” pays off in practice. A Function is versioned and deployed together with its app — you don’t ship or update it independently. That keeps the logic, its configuration schema and its storefront presentation in one release cycle, so a change to a discount rule and the UI that configures it move together. Maintenance follows Shopify’s platform: Functions track the extension-point API versions, and updates are a coordinated app release rather than a scattered patch across a theme and a server.
Common misconceptions
- “Functions replace apps.” No — they’re deployed inside apps and have no UI of their own.
- “An app alone can enforce a discount.” Only if it uses a Function (or native discounts). App JavaScript on the storefront shows an offer; it can’t guarantee the price at checkout.
- “Functions can do anything server-side.” No — they run at defined extension points (discounts, cart/checkout validation, payment, delivery). They’re not a general-purpose backend.
- “You need Shopify Plus for Functions.” Functions are broadly available and are the modern successor to the old Plus-only Scripts.
Getting the app/Function boundary right is a distinct skill from theme work, and it’s the difference between an architecture that’s enforced and one that leaks. If you want a second opinion on where a rule belongs, a free profit audit is a good place to start, or see how we build custom apps and Shopify Functions.
Not sure what belongs in a Function versus an app? Tell us what you’re trying to build — we’ll design the right architecture, with checkout-critical logic in a Function and the rest in the app. See Custom Apps & Shopify Functions or get a free profit audit.