crumpled paper texture
Back to Shopify Articles
ShopifyNovember 11, 2026(Updated: Nov 11, 2026)14 min read

Shopify App Development: A Practical Guide to Building Your First App with Remix

A practical guide to building your first Shopify app: scaffolding with the CLI on the recommended Remix template, OAuth and sessions, your first GraphQL call, scopes, reliable webhooks, billing, deployment, and getting through App Store review.

Yash Nandvana

Yash Nandvana

Full Stack Developer

Shopify App Development Architecture Diagram

1. Introduction

A theme section changes how one store looks. An app changes what any number of stores can do. That is the whole difference, and it is why app development is a different discipline from theme development: a section is Liquid and JSON shipped inside a theme, while an app is a standalone web service that talks to Shopify's APIs, lives inside the admin as an embedded page, and has to survive installs, uninstalls, OAuth, webhooks, billing, and review on stores you will never see.

This guide walks through building a first Shopify app the way Shopify intends it to be built in 2026: scaffolded with the Shopify CLI, built on the recommended Remix template, authenticated through the template's session handling, and shipped to the App Store. It covers the parts that are genuinely load-bearing — OAuth, sessions, scopes, webhooks, billing, deployment — and the mistakes that get apps rejected in review or broken in production. It assumes you can write JavaScript or TypeScript and have built something with Node.js before. It does not assume you have ever touched the Shopify Partner Dashboard.

2. What You Need Before You Start

Three accounts and two tools, all free:

A Shopify Partner account. Create one at partners.shopify.com. This is where your app is registered, where development stores are created, and where the App Store listing lives. A Partner account is free and separate from a merchant account.
A development store. Create one from the Partner Dashboard. Development stores are free, cannot take real orders, and are where you install the app while building. Create the store before you scaffold, so the CLI can link the app to it on first run.
Node.js LTS. The CLI and the template need a recent Node.js LTS release. Check with node --version and upgrade if you are on an old major version.
The Shopify CLI. Install it globally with npm install -g @shopify/cli and verify with shopify version. The CLI scaffolds the app, runs it locally against your development store with a secure tunnel, and deploys app configuration and extensions from the terminal.

That is the full prerequisite list. There is no paid plan, no test credit card, and no separate staging environment to buy. The development store is the staging environment.

3. Scaffold with the CLI

From an empty folder, run:

bash
npm init @shopify/app@latest

The CLI asks for a project name and which starting point you want. Pick Start with Remix (recommended). The alternative is an extension-only app, which is for apps that are nothing but extensions with no admin UI — not what a first app should be.

Two notes on the template name, because it confuses people. First, Remix has merged into React Router v7, and Shopify's template follows that lineage — if your scaffold mentions React Router instead of Remix, you are on the current path, not a wrong one. The model in this guide — loaders that fetch on the server, actions that handle form submissions, nested routes — is the same either way. Second, Shopify previously shipped a Next.js template and moved away from it. Remix's server-first model fits Shopify's requirements better: authenticated server-side API calls are the default shape of the code, not something bolted on.

Once scaffolding finishes, run the dev command:

bash
npm run dev

On first run the CLI does several things at once: it registers the app against your Partner account, starts the Remix dev server on localhost, creates a secure tunnel (Cloudflare) so Shopify can reach your local machine over HTTPS, and prints an install link for your development store. Open that link, install the app on the dev store, and you are looking at the template's index page inside the Shopify admin. The app already works at this point — it is a working shell with OAuth, sessions, and an embedded UI. Everything after this is your feature.

4. What the Template Gives You

Before writing any feature code, know what the scaffold already handles, because these are the parts that are fiddly and security-relevant, and hand-rolling them is where first apps go wrong:

The full OAuth flow. Install, callback, HMAC verification, the state nonce, token exchange, and token storage. Every step where a hand-rolled implementation goes subtly wrong is already covered.
Session storage. A pluggable store with a SQLite default for local development. The template uses Prisma to store sessions. SQLite is a local convenience, not a deployment target — production needs a real database.
Embedding and App Bridge. Correct iframe behaviour, session tokens on every request, and navigation that keeps the Shopify admin's chrome in sync. App Bridge is the JavaScript library that lets your embedded app communicate with the admin; the current 4.x generation authenticates with session tokens rather than the older redirect-based flow.
An authenticated API client. authenticate.admin(request) in a loader or action returns a client with the access token already attached, plus rate-limit-aware behaviour.
Webhook registration and verification. Subscriptions declared in shopify.app.toml are registered on install, and incoming requests are HMAC-verified with the raw body handled correctly.
Polaris. Shopify's design system, so the app looks like part of the admin instead of a foreign page pasted into an iframe.

What the template does not give you: a data model, background jobs, a queue, monitoring, billing logic, or any opinion about how you reconcile state when a webhook goes missing. Those are yours to build, and sections 6 to 10 cover the decisions that matter.

The template's configuration lives in shopify.app.toml at the project root. It declares the app's name, the API version it targets (a dated release such as 2026-04), the access scopes it requests, the webhook subscriptions it registers, and the URLs Shopify should use. npm run deploy pushes this configuration to Shopify, so what is declared in the repository is what is live. Treat the file as the app's contract with the platform, not as boilerplate to ignore.

5. OAuth and Sessions: The Part You Must Not Rebuild

When a merchant installs your app, Shopify redirects their browser to your app with install parameters, your app redirects them to Shopify to grant the scopes in shopify.app.toml, and Shopify redirects back with an authorization code that your app exchanges for an access token. That token is stored in the session store, keyed by shop, and used for every API call on that shop's behalf.

The template handles all of this. Your job is to not break it, and the rules are short:

Never store tokens in the browser. Access tokens live server-side in the session store. The embedded frontend talks to your own loaders and actions, which call authenticate.admin(request) and use the server-side token.
Use offline tokens for background work. The token obtained at install is tied to the install session. Work that happens without a merchant sitting in the admin — a scheduled sync, a webhook handler that calls the API — needs an offline access token, which the template obtains during installation. If a webhook handler fails with an authentication error, a missing offline token is the first thing to check.
Never log tokens. Not in development, not in error reports. A leaked access token is a live credential for a merchant's store.
Reinstalls are normal. Merchants uninstall and reinstall. Your session store should tolerate a new session for a shop that already has rows in your database, and your onboarding should be idempotent — running it twice should not duplicate data or charge twice.

If you are tempted to replace the template's auth with a hand-rolled version because it looks simpler, remember what it handles: HMAC verification of the install redirect, the state nonce that prevents cross-site request forgery, token exchange, and session persistence. The twelve things everyone gets wrong about Shopify authentication are already right in the scaffold. Keep them.

6. Your First API Call

The shape of every feature in a Shopify app is the same: a Remix loader authenticates, calls the Admin API, and returns data to the component. Here is the template's pattern, fetching the shop's first ten products:

js
export async function loader({ request }) {
  const { admin } = await authenticate.admin(request);
  const response = await admin.graphql(`#graphql
    query {
      products(first: 10) {
        edges {
          node {
            id
            title
            totalInventory
          }
        }
      }
    }`);
  const { data } = await response.json();
  return json({ products: data.products.edges });
}

Three things to notice. First, there is no token handling in this code — authenticate.admin did it. Second, the Admin API is GraphQL, and the query is the same shape you would write against any GraphQL API: ask for exactly the fields you need. Third, the data is fetched on the server, inside the loader, so the component renders with data already in hand.

Two habits to build from the first call onwards. Check userErrors on every mutation response — GraphQL returns a 200 with errors inside the payload, so a successful HTTP status does not mean the write worked. And respect the cost-based rate limit: every query has a computed cost against a per-app bucket, so a page that fires five heavy queries on every load will throttle under real traffic. The pagination, cost calculation, and userErrors patterns are covered in depth in the GraphQL Admin API guide linked at the end of this post.

7. Scopes: Ask for the Least You Need

Access scopes are declared in shopify.app.toml:

toml
[access_scopes]
scopes = "read_products,write_products"

Scopes are the app's permission slip, and three rules govern them:

Ask for the minimum. A scope you do not need is a liability in review and a reason for a merchant to hesitate at install. Read-only where read-only is enough; write access only for the features that write.
Adding a scope later forces re-consent. When the installed app's scopes change, merchants must approve the new scopes before the app can use them. Plan the scope list before launch, and treat adding one as a product decision with a rollout, not a one-line config change.
Scopes and features must match. The review team checks that the app actually uses what it asks for. A scope with no corresponding feature is a rejection reason.

Decide the scope list when you decide the feature list. If a feature is speculative, leave its scope out until the feature ships.

8. Webhooks Done Right

Most of an app's real work happens outside the admin page: a product is updated, an order is created, a customer is deleted, and your app has to react. That is what webhooks are for, and they are also where first apps most often break in production.

Declare subscriptions in shopify.app.toml and they are registered when the app is installed. Then handle the four things that make webhooks reliable:

Verify the HMAC signature on the raw body. Shopify signs every webhook with your app's secret. Verification must run against the raw request body, before any JSON parsing re-serializes it — parsed-and-re-stringified JSON does not byte-match the signature. This is covered in detail, with the raw-body handling and a working verifier, in the webhooks guide linked below.
Answer fast, work later. Shopify expects a 200 within seconds and retries delivery if it does not get one. The handler should verify, enqueue the work, and return. The actual processing — API calls, database writes — happens in a background job.
Make processing idempotent. Shopify retries deliveries, so the same event can arrive twice. A job that creates a row for an event it already processed will create duplicates. Key your processing on the webhook's ID or the resource ID plus the event timestamp, and skip work you have already done.
Subscribe to the mandatory webhooks. Public apps must handle the GDPR webhooks — customers/data_request, customers/redact, and shop/redact — plus app/uninstalled so you can clean up a shop's data and stop billing when the app is removed. An app without these does not pass review.

The template gives you registration and verification. The queue, the idempotency, and the reconciliation when a webhook never arrives are yours. If your app's correctness depends on never missing an event, add a periodic reconciliation job that compares your stored state against the API — webhooks are a notification mechanism, not a guarantee.

9. Billing: Charge Through the Billing API

If the app is paid, charges go through Shopify's Billing API — never through an external payment link. The merchant approves the charge inside the admin, Shopify takes its revenue share, and the charge appears on the merchant's Shopify invoice. The standard shape is a recurring subscription created with appSubscriptionCreate, with a trial period if you offer one.

Practical points:

Test on a development store first. Test charges let you exercise the full purchase flow without real money moving.
Gate features on the active subscription, not on install. Check the subscription status on each request that serves a paid feature. A merchant whose payment failed or who cancelled should lose access cleanly, with a clear message, not a crash.
Handle the uninstall path. When app/uninstalled arrives, stop the subscription and stop billing. Continuing to charge an uninstalled shop is the fastest way to a bad review and a policy violation.
Price the app for the merchant it serves. A store doing ten orders a month and a store doing ten thousand have different willingness to pay. The listing should say plainly what the price is and what the plan includes — review checks this, and merchants decide on it.

Free apps skip all of this, but the moment a price exists, the billing flow is part of the app and will be tested in review.

10. Deploying: A Real Host and a Real Database

The tunnel the CLI creates is for local development only. A production app needs three things the scaffold does not provide:

A host that runs Node.js and stays up. Any platform that runs a long-lived Node process works — a VPS, a container host, a platform-as-a-service. The app is a standard Remix server, so deployment is a standard Node deployment, not something Shopify-specific.
A real database. The template's SQLite session store is for local development. In production, sessions, shop records, and your feature data need a hosted database. Prisma makes the switch a configuration change, but make it before the first real install, not after — migrating live session rows under pressure is avoidable.
Environment variables set on the host. At minimum SHOPIFY_API_KEY, SHOPIFY_API_SECRET, and SHOPIFY_APP_URL pointing at the production URL. The app URL must be HTTPS and must match what is registered in the Partner Dashboard, or OAuth redirects will fail.

Deployment order matters: deploy the code, set the environment, run shopify app deploy to push the configuration, then install the app on a fresh development store and click through every flow as if you were the reviewer. If anything in the OAuth flow, the billing flow, or the webhook handlers depends on the production URL, that fresh-install test is where it breaks — which is the point of doing it before review, not during it.

11. The App Store Listing and Review

A public app is not live when the code deploys. It is live when the listing is approved, and review tests the app the way a merchant would:

Install it on a fresh development store. The reviewer does this. If onboarding assumes data, accounts, or configuration that only exists on your dev store, it fails here.
Listing assets and copy. A clear name, an honest description, screenshots of the actual app, a privacy policy URL, and a support contact. Screenshots must show the real UI, not mockups.
Every flow works. Install, onboarding, the core feature, billing approval, uninstall. Broken links, dead buttons, and placeholder text are rejection reasons.
The app does what the listing promises. Features in the copy that do not exist in the app are a rejection reason, and so are scopes with no matching feature.

Common rejection reasons, in order of how often they appear: missing or broken GDPR webhooks, billing that does not work on a test store, scopes the app does not use, and an onboarding flow that only works on the developer's own store. Fix those four before submitting and the review is usually a formality. Expect the review to take days, not hours, and budget a round of fixes into the launch plan.

Platform note, November 2026: Shopify is rolling out a mandatory IP allowlist for public apps. Bulk operations are being blocked from mid-November 2026 for apps without one, and existing installs keep working until 1 April 2027. If your app uses bulk operations, add your servers' IP addresses in the Partner Dashboard's App Security Hub before you ship — it is a one-time configuration step that is easy to miss and expensive to discover through a blocked operation.

12. Checklist

Partner account created; development store ready before scaffolding.
Scaffolded with the CLI on the recommended Remix template; npm run dev installs cleanly on the dev store.
shopify.app.toml reviewed: correct api_version, minimum scopes, webhook subscriptions declared.
OAuth left to the template: tokens server-side, offline tokens for background work, nothing logged.
First API call works through authenticate.admin; mutations check userErrors; queries stay inside the cost budget.
Webhooks verify HMAC on the raw body, answer fast, process idempotently, and include the GDPR set plus app/uninstalled.
Billing goes through the Billing API; paid features gate on the active subscription; uninstall stops billing.
Production host + real database configured; environment variables set; shopify app deploy run.
Fresh-install test on a new development store: install, onboarding, feature, billing, uninstall all work.
Listing honest and complete: real screenshots, privacy policy, support contact, price stated plainly.
IP allowlist set in the App Security Hub if the app uses bulk operations.

Conclusion

Shopify app development looks like a lot of surface area — CLI, template, OAuth, GraphQL, webhooks, billing, review — but almost all of it is the template's job, and the template is good at it. The work that is actually yours is smaller and more specific: pick the minimum scopes, write the feature, make the webhooks idempotent, charge through the Billing API, and deploy to a host with a real database. Everything else is configuration and verification.

The fastest way to learn it is to build the loop once, end to end: scaffold, install on a development store, make one API call, register one webhook, deploy, and install again on a fresh store. After that loop, the second feature is just more of the same shape — a loader, an action, a webhook handler, a Polaris page — and the app grows by repeating a pattern you have already proven works. Ship the small version, get it through review, and let real merchants tell you what the next feature should be.

Next Recommended Reads

#Shopify#Shopify App Development#Remix#Shopify CLI#OAuth#GraphQL#Webhooks#Polaris#JavaScript#E-commerce
Yash Nandvana

Yash Nandvana• Full Stack Developer

Full Stack & Shopify Developer building scalable web apps, developer tools, and AI solutions.

Learn more about Yash
wingsLogo

FROM CONCEPT TO CREATION

LET'S MAKE IT HAPPEN!

I'm available for full-time roles & freelance projects.

I thrive on crafting dynamic web applications, and
delivering seamless user experiences.

>_~/terminal