When merchants ask us to build something Shopify doesn’t do out of the box, the first question we ask is: what type of app do you need? The answer determines the entire architecture — OAuth flow, API access levels, distribution method, and ongoing maintenance model.
Most non-developers assume “an app is an app.” In practice, Shopify has three distinct app types, each with different capabilities, constraints, and review processes. Building the wrong type wastes weeks.
The Three App Types
1. Public Apps
What they are: Apps distributed through the Shopify App Store. Any merchant can install them. Shopify reviews all public apps before they’re listed.
How access works: OAuth 2.0 — merchants install the app through the App Store, grant permissions via a consent screen, and the app receives a permanent access token scoped to that shop.
Who builds them: ISVs (independent software vendors), agencies building products, developers who want to sell to multiple merchants.
Key characteristics:
- Requires Shopify Partner account
- Must pass Shopify’s app review (can take 1–6 weeks)
- Access to all public Shopify APIs
- Billing must use Shopify’s Billing API (for App Store apps)
- Must be built with an HTTPS server that handles OAuth callbacks
Official reference: Shopify Public Apps documentation
2. Custom Apps
What they are: Apps built for a single merchant only. Installed directly from the Shopify Admin — no App Store listing, no Shopify review process.
How access works: Custom apps use static Admin API access tokens generated in the Shopify Admin. No OAuth flow — the merchant creates the app in their admin and gets a token.
Who builds them: Agencies building merchant-specific solutions, internal development teams, integrations with proprietary systems.
Key characteristics:
- No App Store listing or Shopify review
- Faster to deploy (no review queue)
- Static access tokens (no OAuth needed)
- Can only be installed on ONE specific shop
- Billing handled outside Shopify (invoice the merchant directly)
- Access to all public APIs the merchant grants
Official reference: Custom apps in Shopify Admin
3. Private Apps (Legacy — Deprecated)
Shopify deprecated private apps in early 2022. If you’re still using a private app, it still works, but you cannot create new ones. Shopify replaced them with Custom Apps.
If you have a legacy private app integration, migrate it to a Custom App — the access token format changed and some API functionality is restricted for private apps going forward.
Reference: Private apps deprecation notice
Choosing the Right Type: Decision Tree
Does this app need to work on multiple Shopify stores?
- Yes → Public App
- No → Custom App
Does the merchant need to install it through the App Store?
- Yes → Public App
- No, it’s just for us → Custom App
Do you need Shopify Billing API (charging merchants through Shopify)?
- Yes → Public App (Billing API only works in public/unlisted apps)
- No → Custom App
Is speed of deployment critical (weeks vs months)?
- Custom App can be live in days; Public App review takes 1–6 weeks minimum
Common use cases by type:
| Use Case | App Type |
|---|---|
| Sell an app to thousands of merchants | Public |
| Build a custom order management tool for one client | Custom |
| Connect a merchant’s ERP to Shopify | Custom |
| Build a loyalty/rewards system to distribute | Public |
| Internal analytics dashboard for one brand | Custom |
| Migrate from an old private app | Custom |
Building a Public App: Technical Requirements
Public apps must meet Shopify’s technical requirements. Here’s what that means in practice.
OAuth 2.0 Flow
Public apps authenticate merchants through OAuth. When a merchant installs your app:
- Shopify redirects them to your app’s install URL with an HMAC-signed request
- Your app validates the HMAC signature
- Your app redirects the merchant to Shopify’s OAuth consent screen with the required scopes
- Merchant approves — Shopify redirects back to your callback URL with a
code - Your app exchanges the code for a permanent access token
- Store the access token securely (encrypted, per-shop)
// Step 1: Validate incoming install request
import { createHmac } from 'crypto';
function validateHmac(query) {
const { hmac, ...rest } = query;
const message = Object.keys(rest)
.sort()
.map(key => `${key}=${rest[key]}`)
.join('&');
const expected = createHmac('sha256', process.env.SHOPIFY_API_SECRET)
.update(message)
.digest('hex');
return hmac === expected;
}
// Step 2: Build auth URL
function buildAuthUrl(shop, state) {
const scopes = 'read_orders,write_products,read_customers';
const redirectUri = `https://yourapp.com/auth/callback`;
return `https://${shop}/admin/oauth/authorize?client_id=${process.env.SHOPIFY_API_KEY}&scope=${scopes}&redirect_uri=${redirectUri}&state=${state}`;
}
// Step 3: Exchange code for token
async function getAccessToken(shop, code) {
const response = await fetch(`https://${shop}/admin/oauth/access_token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: process.env.SHOPIFY_API_KEY,
client_secret: process.env.SHOPIFY_API_SECRET,
code,
}),
});
const { access_token } = await response.json();
return access_token;
}
The Remix Stack (Shopify’s Recommended Framework)
Shopify’s official recommendation since 2023 is to build apps with the Remix framework using the @shopify/shopify-app-remix package. This handles the entire OAuth flow, session management, and App Bridge integration automatically.
# Create a new Shopify app with Remix
npm init @shopify/app@latest
# Select: Start by building a new app
# Select: Remix
The Remix template gives you:
- OAuth flow handled by
shopifyApp()middleware - Session storage (SQLite for dev, configurable for production)
- App Bridge integration for embedded admin UI
- Polaris component library pre-configured
- TypeScript by default
Reference: Shopify Remix app template
Required API Scopes
When requesting scopes in your OAuth flow, request only what you need. Shopify reviewers flag apps that request excessive permissions.
// Minimal scopes for common use cases:
// Read orders only
const scopes = 'read_orders';
// Full order management
const scopes = 'read_orders,write_orders';
// Product management
const scopes = 'read_products,write_products';
// Customer data (requires justification in app review)
const scopes = 'read_customers';
// Store information
const scopes = 'read_content,read_themes';
Full scopes list: Shopify API access scopes
Building a Custom App: Step-by-Step
Custom apps are much simpler — no OAuth, no review, no deployment server required for many use cases.
Step 1: Create the App in Shopify Admin
- Log into the merchant’s Shopify Admin
- Settings → Apps and sales channels → Develop apps
- Click Create an app
- Give it a name (e.g., “ERP Integration”)
- Click Configure Admin API scopes → select the permissions you need
- Click Install app → confirm
Step 2: Get the Access Token
- After installing, click API credentials
- Under Admin API access token → Reveal token once — copy it immediately (shown once only)
- Store it securely (environment variable, secret manager)
Step 3: Make API Calls
// Custom app API call — no OAuth, just your static token
const SHOP = 'your-store.myshopify.com';
const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
// REST Admin API (legacy but still works)
const response = await fetch(
`https://${SHOP}/admin/api/2024-10/orders.json?status=any&limit=10`,
{
headers: {
'X-Shopify-Access-Token': TOKEN,
'Content-Type': 'application/json',
},
}
);
const { orders } = await response.json();
// GraphQL Admin API (recommended for new integrations)
const graphqlResponse = await fetch(
`https://${SHOP}/admin/api/2024-10/graphql.json`,
{
method: 'POST',
headers: {
'X-Shopify-Access-Token': TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `{
orders(first: 10, query: "status:any") {
edges {
node {
id
name
totalPriceSet { shopMoney { amount currencyCode } }
}
}
}
}`,
}),
}
);
Reference: Shopify Admin API GraphQL
The Shopify App Review Process (Public Apps)
If you’re building a public app, plan for the review process. Here’s what Shopify checks:
Technical Requirements
- App loads within 3 seconds on a standard connection
- Embedded admin UI uses App Bridge (no iframes, no pop-ups outside App Bridge)
- OAuth flow is CSRF-protected (state parameter)
- Webhook endpoints respond within 5 seconds
- App handles GDPR webhooks:
customers/redact,shop/redact,customers/data_request
Design Requirements
- Embedded pages use Polaris components (Shopify’s design system)
- No dark patterns — pricing must be clearly displayed
- Settings page must exist with clear descriptions of what the app does
GDPR Webhooks (Required)
Every public app must handle three GDPR webhooks:
// These are mandatory — your app will be rejected without them
// 1. Customer data request (respond within 30 days)
app.post('/webhooks/customers/data_request', async (req, res) => {
// Find and return all data you store for this customer
res.status(200).send();
});
// 2. Customer redact (delete customer data)
app.post('/webhooks/customers/redact', async (req, res) => {
// Delete all PII for this customer from your database
res.status(200).send();
});
// 3. Shop redact (delete all shop data after uninstall)
app.post('/webhooks/shop/redact', async (req, res) => {
// Delete all data associated with this shop
res.status(200).send();
});
Reference: GDPR webhooks for Shopify apps
Billing API: Public Apps Only
If you’re building a public app and want to charge merchants, you must use Shopify’s Billing API. Shopify takes a 20% revenue share.
// Create a subscription (using @shopify/shopify-app-remix)
import { authenticate } from '../shopify.server';
export async function action({ request }) {
const { billing } = await authenticate.admin(request);
// Check if merchant already has a subscription
const { hasActivePayment, appSubscriptions } = await billing.check({
plans: ['Professional Plan'],
isTest: false,
});
if (!hasActivePayment) {
// Redirect to subscription confirmation
await billing.request({
plan: 'Professional Plan',
isTest: false,
returnUrl: 'https://yourapp.com/admin',
});
}
}
Billing types:
- Recurring — monthly or annual subscription
- One-time — single charge
- Usage-based — charge per unit (orders, API calls, etc.)
Reference: Shopify Billing API
Deployment
Custom Apps
Since there’s no user-facing OAuth flow, custom apps can be simple scripts, serverless functions, or lightweight Node.js apps. Common deployment targets:
- AWS Lambda — for event-driven integrations (webhook handlers)
- Railway / Fly.io — for always-on apps with a UI
- Vercel — for Remix-based custom apps
Public Apps
Must be always-on with low latency. Shopify tests response times during review.
- Fly.io — recommended by Shopify (used in their starter templates)
- Railway — simple deployment, good for Remix apps
- AWS / GCP — for enterprise-scale apps
Common Questions
Can I convert a custom app to a public app later? Not directly — they’re different authentication models. You’d need to rebuild the OAuth flow and go through app review. Plan which type you need upfront.
Do I need a server for a custom app? No — if you’re just syncing data on a schedule, a serverless function (Lambda, Cloud Functions) with a static access token is sufficient. You only need a persistent server if you handle webhooks or have an admin UI.
What’s the difference between the REST and GraphQL Admin APIs? Both give you access to the same data. GraphQL is more efficient (request exactly the fields you need), better for complex queries, and Shopify’s focus for new features. REST still works for everything but some new endpoints are GraphQL-only.
We build custom Shopify apps for merchants who’ve outgrown what the App Store offers. Book a free consultation to discuss your requirements and get an honest scope estimate.