From shopify-pack
Provides Shopify app reference architecture using Remix, Prisma session storage, official templates, project structure, OAuth, webhooks, and extensions.
How this skill is triggered — by the user, by Claude, or both
Slash command
/shopify-pack:shopify-reference-architectureThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Production-ready architecture based on Shopify's official Remix app template. Covers project structure, session storage with Prisma, extension architecture, and the recommended app patterns.
Production-ready architecture based on Shopify's official Remix app template. Covers project structure, session storage with Prisma, extension architecture, and the recommended app patterns.
my-shopify-app/
├── app/
│ ├── routes/
│ │ ├── app._index.tsx # Main app dashboard
│ │ ├── app.products.tsx # Product management page
│ │ ├── app.settings.tsx # App settings
│ │ ├── auth.$.tsx # OAuth catch-all route
│ │ ├── auth.login/
│ │ │ └── route.tsx # Login page
│ │ └── webhooks.tsx # Webhook handler
│ ├── shopify.server.ts # Shopify API config (singleton)
│ ├── db.server.ts # Database connection
│ └── root.tsx
├── extensions/
│ ├── theme-app-extension/ # Theme blocks for Online Store
│ │ ├── blocks/
│ │ │ └── product-rating.liquid
│ │ └── locales/
│ ├── checkout-ui/ # Checkout UI extension
│ └── product-discount/ # Shopify Function
├── prisma/
│ ├── schema.prisma # Database schema
│ └── migrations/
├── shopify.app.toml # App configuration
├── shopify.web.toml # Web process config
├── remix.config.js
└── package.json
// app/shopify.server.ts — the heart of the app
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.SHOPIFY_SCOPES?.split(","),
apiVersion: "2024-10",
distribution: AppDistribution.AppStore, // or SingleMerchant
sessionStorage: new PrismaSessionStorage(prisma),
webhooks: {
APP_UNINSTALLED: {
deliveryMethod: "http",
callbackUrl: "/webhooks",
},
PRODUCTS_UPDATE: {
deliveryMethod: "http",
callbackUrl: "/webhooks",
},
},
hooks: {
afterAuth: async ({ session }) => {
// Register webhooks after successful auth
shopify.registerWebhooks({ session });
},
},
future: {
unstable_newEmbeddedAuthStrategy: true,
},
});
export default shopify;
export const apiVersion = "2024-10";
export const addDocumentResponseHeaders = shopify.addDocumentResponseHeaders;
export const authenticate = shopify.authenticate;
export const unauthenticated = shopify.unauthenticated;
export const login = shopify.login;
export const registerWebhooks = shopify.registerWebhooks;
export const sessionStorage = shopify.sessionStorage;
// prisma/schema.prisma
datasource db {
provider = "sqlite" // or "postgresql" for production
url = env("DATABASE_URL")
}
model Session {
id String @id
shop String
state String
isOnline Boolean @default(false)
scope String?
expires DateTime?
accessToken String
userId BigInt?
firstName String?
lastName String?
email String?
accountOwner Boolean @default(false)
locale String?
collaborator Boolean? @default(false)
emailVerified Boolean? @default(false)
}
// Your app's custom models
model ProductSync {
id String @id @default(cuid())
shop String
productId String
lastSynced DateTime @default(now())
status String @default("pending")
@@unique([shop, productId])
}
// app/routes/app.products.tsx
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { authenticate } from "../shopify.server";
import { Page, Layout, Card, DataTable } from "@shopify/polaris";
export async function loader({ request }: LoaderFunctionArgs) {
const { admin } = await authenticate.admin(request);
// admin.graphql is a pre-authenticated GraphQL client
const response = await admin.graphql(`{
products(first: 25, sortKey: UPDATED_AT, reverse: true) {
edges {
node {
id
title
status
totalInventory
priceRangeV2 {
minVariantPrice { amount currencyCode }
}
}
}
}
}`);
const data = await response.json();
return json({ products: data.data.products.edges.map((e: any) => e.node) });
}
export default function Products() {
const { products } = useLoaderData<typeof loader>();
return (
<Page title="Products">
<Layout>
<Layout.Section>
<Card>
<DataTable
columnContentTypes={["text", "text", "numeric", "text"]}
headings={["Title", "Status", "Inventory", "Price"]}
rows={products.map((p: any) => [
p.title,
p.status,
p.totalInventory,
`$${p.priceRangeV2.minVariantPrice.amount}`,
])}
/>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}
{% comment %} extensions/theme-app-extension/blocks/product-rating.liquid {% endcomment %}
{% schema %}
{
"name": "Product Rating",
"target": "section",
"settings": [
{
"type": "range",
"id": "max_stars",
"label": "Maximum Stars",
"min": 1,
"max": 5,
"default": 5
},
{
"type": "color",
"id": "star_color",
"label": "Star Color",
"default": "#FFD700"
}
]
}
{% endschema %}
<div class="product-rating" style="--star-color: {{ block.settings.star_color }}">
{% assign rating = product.metafields.custom.rating.value | default: 0 %}
{% for i in (1..block.settings.max_stars) %}
<span class="star {% if i <= rating %}filled{% endif %}">★</span>
{% endfor %}
<span class="rating-text">{{ rating }}/{{ block.settings.max_stars }}</span>
</div>
<style>
.product-rating .star { color: #ccc; font-size: 1.2em; }
.product-rating .star.filled { color: var(--star-color); }
</style>
| Issue | Cause | Solution |
|---|---|---|
| Session not found | DB not migrated | Run npx prisma migrate dev |
| Auth redirect loop | Missing APP_UNINSTALLED handler | Implement webhook to clean sessions |
| Extension not showing | Not deployed | Run shopify app deploy |
| Polaris styles missing | Missing provider | Wrap app in <AppProvider> |
# Fastest way to start — uses official template
shopify app init --template remix
# Or clone directly
npx degit Shopify/shopify-app-template-remix my-shopify-app
cd my-shopify-app
npm install
shopify app dev
For multi-environment setup, see shopify-multi-env-setup.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin shopify-packGuides Shopify app architecture choices: embedded Remix admin apps, Hydrogen headless storefronts, standalone integrations, theme extensions. Use for architecture decisions.
Guides Shopify app development with Remix template: app types, OAuth flow, session tokens, App Bridge, webhooks, extensions, and embedded admin apps.
Build Shopify apps, extensions, and themes using GraphQL Admin API, Shopify CLI, Polaris UI, and Liquid. Covers routing, CLI commands, access scopes, and GraphQL patterns.