How to Build Your First Shopify App in 2026
A complete beginner's guide — from understanding Shopify's app architecture to submitting your finished app to the App Store. No prior Shopify experience required.
Shopify powers over 4 million online stores worldwide, and its app ecosystem is one of the richest in e-commerce. Whether you want to build a tool for your own store, solve a niche merchant problem, or launch a SaaS product on the Shopify App Store, this guide walks you through every step.
By the end, you'll have a working Shopify app running on a development store, and you'll know exactly how to get it reviewed and published. Let's build.
Shopify App Architecture Basics
Before you write a single line of code, you need to understand how Shopify apps actually work. A Shopify app is not a WordPress plugin that lives inside the platform — it's an external web application that communicates with Shopify through APIs.
How Apps Connect to Shopify
Your app lives on your own server (or a serverless platform like Vercel or Fly.io). When a merchant installs your app, Shopify uses OAuth 2.0 to establish a secure connection. After authentication, your app can:
- Read and write store data via the Admin API (REST or GraphQL) — products, orders, customers, inventory, and more
- Display UI inside the Shopify admin using Shopify App Bridge and Polaris (Shopify's React component library)
- React to store events (order placed, product updated) via webhooks
- Extend the storefront with theme app extensions, checkout UI extensions, and more
The Three Types of Shopify Apps
Public Apps
Listed on the Shopify App Store. Any merchant can install them. Must pass Shopify's review process.
Custom Apps
Built for a single store. Created directly in the Shopify admin. Great for client work and bespoke solutions.
Unlisted Apps
Distributed via a direct install link. Not on the App Store. Useful for B2B or invite-only apps.
Key Technologies in 2026
The Shopify development stack has evolved significantly. Here's what you'll be working with:
- Remix — Shopify's default app framework (replaced Next.js as the official template in late 2023)
- Shopify CLI — scaffolds apps, manages dev environments, handles tunneling
- Polaris — Shopify's React design system for building admin UIs that look native
- App Bridge — library that embeds your app inside the Shopify admin
- GraphQL Admin API — the primary API (REST is still supported but GraphQL is preferred)
- Prisma — default ORM for database operations in the app template
📋 Note
Setting Up Your Development Store
You need a Shopify store to test your app against. Shopify provides free development stores for this exact purpose — they have full functionality but can't process real transactions.
Step-by-Step: Create a Dev Store
- Join the Shopify Partner Program — go to
partners.shopify.comand sign up. It's free and gives you access to unlimited development stores. - Create a development store — from your Partner Dashboard, click
Stores → Add store → Development store. Choose "Create a store to test and build". - Configure the store — give it a name (e.g.,
my-app-test-store), select a purpose, and pick your country/region. - Add test data — populate your store with sample products so you have realistic data to work with. You can use Shopify's built-in sample data or import a CSV.
💡 Pro Tip
Enable Developer Preview
Development stores let you toggle "developer preview" for upcoming APIs and features. Go to Settings → Developer preview in your dev store to enable previews for features you want to build against.
App Scaffolding with Shopify CLI
The Shopify CLI is the fastest way to go from zero to a running app. It generates the full project structure, handles OAuth, sets up your database, and even creates a tunnel so Shopify can reach your local server.
Prerequisites
- Node.js 18+ (LTS recommended — check with
node --version) - npm, yarn, or pnpm
- Git installed and configured
- A Shopify Partner account (from Step 2)
Initialize Your App
# Install Shopify CLI globally (if you haven't)
npm install -g @shopify/cli @shopify/app
# Create a new app
shopify app init
# You'll be prompted for:
# → App name: my-first-shopify-app
# → Template: Remix (recommended)
# → Package manager: npm / yarn / pnpmThe CLI generates a complete project structure:
my-first-shopify-app/
├── app/
│ ├── routes/
│ │ ├── app._index.tsx # Main app page
│ │ ├── app.tsx # App layout with nav
│ │ ├── auth.$.tsx # OAuth callback
│ │ └── webhooks.tsx # Webhook handler
│ ├── shopify.server.ts # Shopify API client setup
│ └── db.server.ts # Database (Prisma) setup
├── prisma/
│ └── schema.prisma # Database schema
├── extensions/ # Theme/checkout extensions
├── shopify.app.toml # App configuration
├── remix.config.js
└── package.jsonStart the Dev Server
cd my-first-shopify-app
# Start the development server
shopify app dev
# This command:
# 1. Starts your Remix dev server
# 2. Creates a Cloudflare tunnel (so Shopify can reach localhost)
# 3. Opens your browser to install the app on your dev store
# 4. Hot-reloads on file changesWhen you run shopify app dev for the first time, the CLI will ask you to:
- Log in with your Partner account
- Create or select an app in your Partner Dashboard
- Select a development store to install on
💡 Pro Tip
shopify app dev. The CLI updates your app's config automatically, so you don't need to manually change URLs in the Partner Dashboard.Understanding the Key Files
Let's look at the files you'll work with most:
shopify.app.toml — your app's configuration. Defines API scopes (permissions), webhooks, and extension settings:
name = "my-first-shopify-app"
client_id = "your-client-id"
[access_scopes]
scopes = "read_products,write_products"
[webhooks]
api_version = "2026-01"
[[webhooks.subscriptions]]
topics = ["products/update"]
uri = "/webhooks"app/shopify.server.ts — initializes the Shopify API client. This is where authentication and API configuration lives:
import "@shopify/shopify-app-remix/adapters/node";
import { AppDistribution, shopifyApp } from "@shopify/shopify-app-remix/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(","),
distribution: AppDistribution.AppStore,
// ... session storage, webhooks, etc.
});
export default shopify;Building a Simple App Feature
Let's build something real: a product tagger that automatically adds tags to products based on their price range. This feature teaches you the core patterns you'll use in any Shopify app — reading data from the Admin API, displaying it with Polaris, and writing data back.
Step 4a: Define Your Data Loader
In Remix, you fetch server-side data with a loader function. Let's create a route that loads products from the store:
import { json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import shopify from "../shopify.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
// authenticate() validates the session and returns an admin API client
const { admin } = await shopify.authenticate.admin(request);
// Query products using the GraphQL Admin API
const response = await admin.graphql(`
{
products(first: 20) {
edges {
node {
id
title
variants(first: 1) {
edges {
node {
price
}
}
}
tags
}
}
}
}
`);
const data = await response.json();
return json({
products: data.data.products.edges.map((edge: any) => ({
id: edge.node.id,
title: edge.node.title,
price: parseFloat(edge.node.variants.edges[0]?.node.price || "0"),
tags: edge.node.tags,
})),
});
};Step 4b: Build the UI with Polaris
Polaris components make your app look native inside the Shopify admin. Here's the UI for our product tagger:
import {
Page,
Layout,
Card,
DataTable,
Button,
Banner,
Badge,
} from "@shopify/polaris";
export default function ProductTagger() {
const { products } = useLoaderData<typeof loader>();
const fetcher = useFetcher();
// Determine which tag each product should get
const getRecommendedTag = (price: number) => {
if (price >= 100) return "premium";
if (price >= 50) return "mid-range";
return "budget";
};
const rows = products.map((product: any) => [
product.title,
`$${product.price.toFixed(2)}`,
product.tags.join(", ") || "No tags",
<Badge key={product.id} tone="info">
{getRecommendedTag(product.price)}
</Badge>,
<fetcher.Form method="post" key={product.id}>
<input type="hidden" name="productId" value={product.id} />
<input
type="hidden"
name="tag"
value={getRecommendedTag(product.price)}
/>
<Button submit size="slim">
Apply Tag
</Button>
</fetcher.Form>,
]);
return (
<Page title="Product Tagger">
<Layout>
<Layout.Section>
<Banner tone="info">
This tool suggests price-based tags for your products.
Click "Apply Tag" to add the recommended tag.
</Banner>
</Layout.Section>
<Layout.Section>
<Card>
<DataTable
columnContentTypes={["text", "numeric", "text", "text", "text"]}
headings={["Product", "Price", "Current Tags", "Suggested", ""]}
rows={rows}
/>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}Step 4c: Handle the Form Submission
Add an action function to handle the tag mutation:
import type { ActionFunctionArgs } from "@remix-run/node";
export const action = async ({ request }: ActionFunctionArgs) => {
const { admin } = await shopify.authenticate.admin(request);
const formData = await request.formData();
const productId = formData.get("productId") as string;
const tag = formData.get("tag") as string;
// Use the GraphQL mutation to add tags
const response = await admin.graphql(`
mutation addTags($id: ID!, $tags: [String!]!) {
tagsAdd(id: $id, tags: $tags) {
node {
... on Product {
id
tags
}
}
userErrors {
message
}
}
}
`, {
variables: { id: productId, tags: [tag] },
});
const data = await response.json();
if (data.data.tagsAdd.userErrors.length > 0) {
return json({ error: data.data.tagsAdd.userErrors[0].message });
}
return json({ success: true });
};Step 4d: Add Navigation
Register your new page in the app's navigation sidebar. Open app/routes/app.tsx and add a nav link:
// In your app layout's navigation:
<NavMenu>
<a href="/app" rel="home">Home</a>
<a href="/app/tagger">Product Tagger</a>
</NavMenu>⚠️ Heads Up
read_products and write_products. Requesting unnecessary scopes will get your app rejected in review.Testing & Submitting to the App Store
You've got a working feature. Now let's make sure it's production-ready and get it into the Shopify App Store.
Testing Checklist
- 1Fresh install flow — uninstall and reinstall the app. Does OAuth work smoothly?
- 2Multiple stores — test on at least 2 dev stores with different configurations
- 3Error handling — what happens with API rate limits? Network failures? Empty stores?
- 4Responsive layout — test your embedded app on different screen sizes
- 5Performance — loading data for stores with 1000+ products? Add pagination
- 6Webhook reliability — use the CLI to trigger test webhooks
- 7Uninstall cleanup — handle the app/uninstalled webhook to clean up store data
Deploy Your App
Before submitting, you need to deploy your app to a production server. Popular hosting options:
Fly.io
Simple deployment with the Shopify CLI. Run `shopify app deploy` for one-command deployment.
Heroku
Classic PaaS option. Good if you're already familiar with it. Add the Heroku Postgres addon for your DB.
Railway
Modern PaaS with automatic deployments from GitHub. Includes built-in Postgres.
Vercel + PlanetScale
Serverless option. Great for apps with low to moderate traffic. Requires some config adjustments.
# Deploy to Fly.io (simplest option)
shopify app deploy
# Or build and deploy manually
npm run build
# Then deploy to your hosting provider of choicePrepare Your App Store Listing
A great listing is just as important as great code. You'll need:
- App name and tagline — clear, descriptive, and keyword-rich
- Detailed description — explain the problem you solve, key features, and who it's for
- Screenshots — at least 3 high-quality screenshots showing key features in the Shopify admin
- App icon — 1200×1200px, follows Shopify's design guidelines
- Privacy policy URL — required. Explain what data your app accesses and how you use it
- Support contact — email or URL where merchants can get help
Submit for Review
- Go to
partners.shopify.com → Apps → Your App → Distribution - Select "Shopify App Store" as your distribution method
- Fill in all listing fields (name, description, screenshots, etc.)
- Complete the App submission checklist — Shopify provides this to make sure you haven't missed anything
- Click "Submit for review"
📋 Note
Common Rejection Reasons (and How to Avoid Them)
Excessive API scopes
Only request scopes your app actually uses. Remove any leftover scopes from development.
Missing error handling
Show user-friendly messages when API calls fail. Handle rate limits with retries.
Slow loading times
Paginate large datasets. Use skeleton loading states. Optimize GraphQL queries.
No uninstall webhook
Handle `app/uninstalled` to clean up session data and respect merchant privacy.
Poor mobile experience
Test your embedded UI at narrow widths. Polaris components are responsive by default — don't fight them.
What's Next?
You now have the foundation to build real Shopify apps. Here are natural next steps to level up:
- Add billing — use the Shopify Billing API to charge merchants with recurring subscriptions or one-time charges
- Build theme app extensions — add functionality directly to the storefront without touching theme code
- Implement checkout extensions — customize the checkout experience with Shopify's checkout extensibility
- Add metafields — store custom data on products, orders, and customers
- Integrate with external services — connect your app to third-party APIs for shipping, analytics, marketing, etc.
Want hands-on help?
Building your first app is exciting — and sometimes confusing. If you want a senior Shopify developer to pair-program with you, review your code, or help you debug that one weird OAuth issue, we're here.
Start with a $9 Quick Question, move to a $19 code review, or book a $49 live session with an experienced Shopify developer on ShopCraft. Pick the depth of help you need.
Get Expert Help — From $9