The Shopify App Store has over 13,000 apps. For the vast majority of requirements — reviews, loyalty programmes, email marketing, upselling, shipping rate calculation — there is already an app that does the job well enough. Installing an existing app is faster, cheaper, and transfers the maintenance burden to a dedicated team.
But “well enough” has limits. When your business processes are genuinely unique, when you need deep integration with internal systems, or when off-the-shelf apps introduce unacceptable performance overhead or data handling concerns, building a custom app becomes the right investment. The challenge is knowing which situation you are actually in.
This guide provides a structured framework for making that decision, followed by a technical walkthrough of building a custom Shopify app using the current recommended architecture. We build custom apps as part of our Shopify development and app development services, and the patterns here reflect what we use in production.
The build vs buy decision
The build vs buy decision is fundamentally a total cost of ownership (TCO) analysis, not a feature comparison. An existing app might lack two features you want, but building a custom app to get those features might cost ten times more over three years when you factor in development, hosting, maintenance, and Shopify API version updates.
The TCO calculation
For an existing App Store app, the TCO includes:
- Monthly subscription — typically £10–£300/month depending on the app and plan tier.
- Configuration time — initial setup and ongoing adjustments, usually 2–10 hours.
- Workarounds — any custom code or processes needed to bridge gaps in functionality.
- Performance impact — the cost (in lost revenue) of any page speed degradation from the app’s scripts.
For a custom app, the TCO includes:
- Development cost — typically £5,000–£50,000+ depending on complexity.
- Hosting — £20–£200/month for the app’s backend infrastructure.
- Maintenance — 10–20% of the initial build cost annually for bug fixes, feature additions, and API updates.
- Shopify API version updates — Shopify deprecates API versions every 12 months; your app must update accordingly.
- Opportunity cost — development time spent on the app cannot be spent on other revenue-generating work.
The most common mistake we see is underestimating maintenance costs. Building the app is 40% of the lifetime cost. Maintaining it is the other 60%.
Clear signals to build custom
Based on our experience building custom apps for brands across various sectors, these are the scenarios where building custom consistently delivers better ROI than buying:
1. Deep ERP or PIM integration
When you need bidirectional synchronisation between Shopify and an internal system — an ERP like SAP, NetSuite, or a bespoke warehouse management system — a custom app is usually necessary. Generic integration platforms like Zapier or Celigo handle simple one-directional sync, but complex business logic (conditional pricing, inventory reservation, multi-location fulfilment rules) requires custom middleware.
2. Proprietary business logic
If your pricing, discounting, or product configuration rules are genuinely unique to your business, no off-the-shelf app will implement them correctly. Examples include:
- Trade pricing based on customer-specific agreements stored in an external system.
- Product bundling rules that depend on inventory levels, supplier lead times, and seasonal calendars.
- Subscription logic that varies by product category, customer segment, and delivery region.
3. Eliminating app bloat
Some stores accumulate five or six apps that each contribute one small piece of functionality. The combined performance impact, data fragmentation, and configuration complexity of these apps can exceed the cost of a single custom app that handles all of the functionality in a unified, optimised way.
We recently consolidated a store’s product filtering, search, and recommendation functionality — previously handled by three separate apps loading 400KB of JavaScript — into a single custom Shopify app that loaded 45KB and performed significantly faster.
4. Data sovereignty requirements
When regulatory or contractual requirements mandate that customer data stays within specific jurisdictions or cannot be processed by third parties, a custom app with controlled hosting gives you full authority over data residency and processing.
When not to build
Equally important is knowing when custom development is the wrong choice:
- Commodity functionality — reviews, wishlist, loyalty points, basic email capture. These are solved problems with mature apps.
- Rapidly evolving requirements — if you are not sure what you need yet, building custom locks you into an architecture before you understand the problem. Start with an off-the-shelf app, learn what matters, then build custom if needed.
- No maintenance plan — if you do not have ongoing development capacity (in-house or via an agency retainer), a custom app will degrade as Shopify’s APIs evolve.
- Feature envy — wanting to build it yourself because you think you can do it better, without evidence that the existing options are actually insufficient.
Types of Shopify apps
Before deciding to build, understand the different app types available, as simpler options may meet your needs without a full app:
Shopify Functions
Serverless functions that run within Shopify’s infrastructure. They handle discount logic, payment customisation, delivery rate calculation, and cart/checkout validation. If your requirement falls into one of these categories, a Function is simpler and cheaper than a full app:
// Example: Shopify Function for volume-based discounting
// input.graphql
query Input {
cart {
lines {
quantity
merchandise {
... on ProductVariant {
id
product {
hasTag: inAnyCollection(ids: ["gid://shopify/Collection/123"])
}
}
}
cost {
amountPerQuantity {
amount
}
}
}
}
}
// src/run.js
export function run(input) {
const discounts = [];
for (const line of input.cart.lines) {
if (line.quantity >= 10) {
discounts.push({
targets: [{ cartLine: { id: line.id } }],
value: { percentage: { value: '15.0' } },
message: '15% bulk discount applied'
});
}
}
return { discounts };
}
Theme App Extensions
UI components that inject into the storefront via app blocks in the theme editor. They run client-side and can fetch data from your app’s backend. Suitable for widgets, badges, and interactive UI elements that need to appear on product pages or cart.
Custom apps (full backend)
A hosted application with its own backend, database, and admin UI embedded in the Shopify admin. This is the most flexible option and what most people mean when they say “custom Shopify app”.
Custom storefronts (headless)
A completely separate frontend application. This is a different category entirely and is covered in our metafields guide and headless commerce content.
Modern Shopify app architecture
Shopify’s recommended app architecture has evolved significantly. The current standard uses Remix as the backend framework, App Bridge for admin integration, and Polaris for the admin UI. Understanding this architecture is critical before starting development.
Core components
- Remix backend — handles OAuth, session management, webhook processing, and serves the admin UI.
- App Bridge — JavaScript library that integrates your app’s UI into the Shopify admin, handling navigation, toast messages, modals, and resource pickers.
- Polaris — Shopify’s React component library for building admin UIs that look and feel native to the Shopify admin.
- Shopify API library — handles REST and GraphQL API calls, rate limiting, pagination, and authentication.
- Prisma (or equivalent) — database ORM for storing app-specific data.
Authentication flow
// app/shopify.server.ts - Authentication setup
import "@shopify/shopify-app-remix/adapters/node";
import { AppDistribution, shopifyApp } from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import prisma from "./db.server";
const shopify = shopifyApp({
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
appUrl: process.env.SHOPIFY_APP_URL || "",
scopes: process.env.SCOPES?.split(","),
authPathPrefix: "/auth",
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
future: {
unstable_newEmbeddedAuthStrategy: true,
},
});
export default shopify;
export const authenticate = shopify.authenticate;
Scaffolding with Shopify CLI
The Shopify CLI generates the complete app scaffold with authentication, database, and admin UI pre-configured:
# Create a new app with the Remix template
shopify app init --template remix
# Navigate to the app directory
cd my-shopify-app
# Install dependencies
npm install
# Start the development server with tunnel
shopify app dev
The shopify app dev command starts a local development server, creates a secure tunnel for Shopify to reach your local machine, and opens the app in the Shopify admin. This development experience is significantly better than it was even two years ago — hot module reloading, automatic OAuth handling, and instant webhook forwarding make local development fast.
Project structure
my-shopify-app/
├── app/
│ ├── routes/
│ │ ├── app._index.tsx # Main app page
│ │ ├── app.settings.tsx # Settings page
│ │ ├── webhooks.tsx # Webhook handlers
│ │ └── auth.$.tsx # Auth callback
│ ├── shopify.server.ts # Shopify config
│ └── db.server.ts # Database connection
├── extensions/
│ ├── product-discount/ # Shopify Function
│ └── product-widget/ # Theme App Extension
├── prisma/
│ └── schema.prisma # Database schema
├── shopify.app.toml # App configuration
└── package.json
API integration patterns
Most custom apps interact with Shopify’s Admin API for data operations. Understanding the GraphQL API’s patterns and constraints is essential for building reliable integrations.
GraphQL queries in route loaders
// app/routes/app._index.tsx
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { authenticate } from "../shopify.server";
export async function loader({ request }) {
const { admin } = await authenticate.admin(request);
const response = await admin.graphql(`
query {
products(first: 25, sortKey: UPDATED_AT, reverse: true) {
edges {
node {
id
title
status
totalInventory
priceRangeV2 {
minVariantPrice {
amount
currencyCode
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`);
const { data } = await response.json();
return json({ products: data.products });
}
Handling rate limits
Shopify’s GraphQL Admin API uses a cost-based rate limiting system. Each query has a calculated cost, and you have a bucket of 1,000 points that refills at 50 points per second. For bulk operations, use the Bulk Operations API:
// Bulk operation for processing large datasets
const BULK_QUERY = `
mutation {
bulkOperationRunQuery(
query: """
{
products {
edges {
node {
id
title
variants {
edges {
node {
id
sku
inventoryQuantity
}
}
}
}
}
}
}
"""
) {
bulkOperation {
id
status
}
userErrors {
field
message
}
}
}
`;
Building the admin UI with Polaris
Polaris components ensure your app’s admin interface looks native to Shopify. Using Polaris is not strictly required, but apps that deviate from the design system feel jarring and unprofessional within the Shopify admin context.
// app/routes/app.settings.tsx
import { Page, Layout, Card, FormLayout, TextField, Select, Button } from "@shopify/polaris";
import { useState, useCallback } from "react";
export default function Settings() {
const [syncFrequency, setSyncFrequency] = useState("hourly");
const [apiEndpoint, setApiEndpoint] = useState("");
const handleSave = useCallback(async () => {
const response = await fetch("/api/settings", {
method: "POST",
body: JSON.stringify({ syncFrequency, apiEndpoint }),
headers: { "Content-Type": "application/json" }
});
if (response.ok) {
shopify.toast.show("Settings saved");
}
}, [syncFrequency, apiEndpoint]);
return (
<Page title="Integration Settings">
<Layout>
<Layout.AnnotatedSection
title="Sync Configuration"
description="Configure how often data syncs between Shopify and your ERP."
>
<Card>
<FormLayout>
<Select
label="Sync frequency"
options={[
{ label: "Every 15 minutes", value: "15min" },
{ label: "Hourly", value: "hourly" },
{ label: "Daily", value: "daily" }
]}
value={syncFrequency}
onChange={setSyncFrequency}
/>
<TextField
label="ERP API endpoint"
value={apiEndpoint}
onChange={setApiEndpoint}
type="url"
helpText="The base URL of your ERP's API"
/>
<Button variant="primary" onClick={handleSave}>Save</Button>
</FormLayout>
</Card>
</Layout.AnnotatedSection>
</Layout>
</Page>
);
}
Deployment and hosting
Custom Shopify apps need reliable hosting. The app must be available 24/7, as webhook processing and admin UI access depend on it. Common hosting options include:
- Fly.io — good performance, global edge deployment, straightforward scaling. Our current preferred option for most apps.
- Railway — simple deployment from Git, good for smaller apps with moderate traffic.
- Vercel — excellent for Remix apps with serverless functions, but can be expensive at scale.
- AWS / GCP — maximum control and scalability, but higher operational complexity.
# Deploy to Fly.io
fly launch --name my-shopify-app
fly secrets set SHOPIFY_API_KEY=xxx SHOPIFY_API_SECRET=xxx
fly deploy
# Set up the database
fly postgres create --name my-app-db
fly postgres attach my-app-db
npx prisma migrate deploy
Production checklist
- HTTPS enforced on all endpoints.
- Webhook HMAC verification implemented.
- Database backups configured.
- Error monitoring (Sentry or equivalent) connected.
- Rate limit handling with exponential backoff.
- GDPR webhooks implemented (mandatory).
- Health check endpoint for uptime monitoring.
Long-term maintenance costs
The ongoing maintenance burden is where custom apps catch most teams off guard. Shopify releases a new API version quarterly and deprecates versions after 12 months. This means your app must be updated at least once a year to remain functional.
Annual maintenance tasks
- API version updates — 2–8 hours per quarterly version, depending on breaking changes.
- Dependency updates — Node.js, Remix, Prisma, and other dependencies need regular updates for security and compatibility.
- Shopify platform changes — new admin UI patterns, deprecated features, changed webhook payloads.
- Bug fixes — edge cases that surface over time as your store’s catalogue and order volume grow.
- Feature requests — once the app exists, stakeholders will inevitably want additions.
Budget 15–20% of the initial build cost annually for maintenance. On a £20,000 app, that is £3,000–£4,000 per year — roughly equivalent to a £250–£330/month SaaS subscription, but for a tool that does exactly what you need.
If you need help deciding whether a custom app is the right approach for your store, or you need a custom app built and maintained, get in touch. We work with brands who need bespoke Shopify functionality as part of our ongoing development retainers.