Appearance
Anatomy of the app template
The React Router template is a full-stack app. React Router v7 (framework mode) renders both the pages and the server routes. Prisma stores sessions.
text
my-app/
├─ app/
│ ├─ shopify.server.ts ← configures @shopify/shopify-app-react-router (auth, API version, session storage)
│ ├─ db.server.ts ← Prisma client
│ ├─ routes/
│ │ ├─ app.tsx ← layout for embedded pages (App Bridge + nav)
│ │ ├─ app._index.tsx ← home page inside Shopify Admin
│ │ ├─ auth.$.tsx ← auth callback catch-all
│ │ └─ webhooks.*.tsx ← one route per webhook topic
│ └─ root.tsx
├─ extensions/ ← theme / checkout / Function extensions live here
├─ prisma/schema.prisma ← Session table (+ your own models)
├─ shopify.app.toml ← THE app config: scopes, URLs, webhooks, app proxy
└─ package.jsonshopify.server.ts — the heart
ts
import { shopifyApp, ApiVersion, AppDistribution } from "@shopify/shopify-app-react-router/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 || "",
apiVersion: ApiVersion.October25, // pin a version; bump it each quarter
scopes: process.env.SCOPES?.split(","),
appUrl: process.env.SHOPIFY_APP_URL || "",
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
});
export const authenticate = shopify.authenticate;
export default shopify;API version enum
The enum name tracks Shopify's quarterly versions (2026-10 is current stable). Check which enum values your installed package version exports.
Every route starts with authenticate
tsx
// app/routes/app._index.tsx
export const loader = async ({ request }) => {
const { admin, session } = await authenticate.admin(request);
const res = await admin.graphql(`{ shop { name } }`);
return { shop: (await res.json()).data.shop };
};authenticate.admin checks the session token, exchanges it for an access token if needed, and hands you a ready-to-use admin.graphql client.
| Helper | Use it in |
|---|---|
authenticate.admin(request) | Embedded admin pages and their actions |
authenticate.webhook(request) | Webhook routes (verifies HMAC) |
authenticate.public.appProxy(request) | App proxy routes called from the storefront |
authenticate.public.checkout(request) | Requests from checkout UI extensions |
shopify.app.toml
toml
client_id = "abc123"
name = "Order Notes Notifier"
application_url = "https://example.com"
embedded = true
[access_scopes]
scopes = "read_orders,write_products"
[auth]
redirect_urls = ["https://example.com/auth/callback"]
[webhooks]
api_version = "2026-10"
[[webhooks.subscriptions]]
topics = ["app/uninstalled"]
uri = "/webhooks/app/uninstalled"
[[webhooks.subscriptions]]
compliance_topics = ["customers/data_request", "customers/redact", "shop/redact"]
uri = "/webhooks/compliance"Changing scopes or webhooks in the toml and running shopify app deploy updates them for every install. You don't need per-shop API calls.
Multiple environments
Keep shopify.app.toml (prod) and shopify.app.staging.toml, which point at two different apps. Switch between them with shopify app config use staging.
Check yourself
- What three things does
authenticate.admindo for you? - You add
read_customersto the toml. What happens to existing installs after deploy?