"Can you build us a custom discount?" is one of the most common things merchants ask us, and the honest answer is always "yes, but where it should live depends on a few things." Cart logic, shipping rules, and discounts can run as a Shopify Function, inside a full app, or as a combination of both. Picking wrong means either a frustrating ceiling later or a lot of machinery you didn't need.
Here's how we actually decide.
What a Function is good at
A Shopify Function is a small piece of logic that runs inside Shopify's own systems: the discount engine, the shipping rate calculation, the checkout. It's fast, it runs server-side at Shopify's scale, and it doesn't need infrastructure you have to host and babysit.
That makes Functions excellent for rules: "spend over £x in this collection, get y% off," "hide express shipping for these postcodes," "reorder payment methods for B2B customers." Deterministic logic, decided at checkout, with inputs Shopify already has.
Where a Function hits its ceiling
Functions are deliberately constrained. They have tight execution limits, they can't call out to your own database or a third-party API mid-checkout, and they can't hold long-running state. The moment your rule needs to remember something, fetch something external, or show a rich admin UI, you've outgrown a Function on its own.
That's the signal you need an app around it.
A rough decision table
This is the shorthand we use:
| What you need | Function alone | App (+ Function) |
|---|---|---|
| Discount/shipping rule from cart data | ✅ | overkill |
| Merchant-configurable settings UI | ⚠️ limited | ✅ |
| Lookups against your own DB or an API | ❌ | ✅ |
| Logic that runs at checkout, fast | ✅ | ✅ (Function does the checkout bit) |
| Reporting, history, webhooks, jobs | ❌ | ✅ |
The pattern in that last column matters: it's rarely "app instead of Function." It's an app that owns the configuration, data, and UI, with a Function as the thin, fast piece that actually runs at checkout. The app is the brain; the Function is the reflex.
A concrete example
Say you want tiered wholesale pricing that depends on a customer's lifetime spend.
- The spend total lives in your data and changes over time → that's app territory (a webhook keeps a tally, a job recalculates tiers).
- The "apply the right tier at checkout" step has to be instant and run inside Shopify → that's a Function, fed the customer's current tier as input.
Neither piece can do the job alone. Together they're clean.
The trade-off worth weighing
A Function is cheaper to build and has almost nothing to maintain: no servers, no uptime to worry about. An app is more capable but it's a thing you now own: hosting, updates, Shopify API version bumps twice a year.
So our default is: push as much as possible into a Function, and only add an app for the parts that genuinely can't live there. Less surface area, less to break, less to pay for over time.
If you've got a rule in mind and you're not sure which side of the line it falls on, send us the details. Working out the boundary is half the value.