Help us improve
Share bugs, ideas, or general feedback.
From shopify-commerce
Builds Node.js backends for Shopify apps using Remix and @shopify/shopify-app-remix for OAuth, Prisma sessions, API proxying, webhooks, and deployment.
npx claudepluginhub orcaqubits/agentic-commerce-skills-plugins --plugin shopify-commerceHow this skill is triggered — by the user, by Claude, or both
Slash command
/shopify-commerce:node-backendThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Fetch live docs**:
Guides Shopify app development with Remix template: app types, OAuth flow, session tokens, App Bridge, webhooks, extensions, and embedded admin apps.
Provides Shopify app reference architecture using Remix, Prisma session storage, official templates, project structure, OAuth, webhooks, and extensions.
Provides expert patterns for Shopify app development including Remix/React Router setup, embedded apps with App Bridge, webhook handling, GraphQL Admin API, Polaris components, billing, and app extensions.
Share bugs, ideas, or general feedback.
Fetch live docs:
site:shopify.dev app remix server for Remix app server patternssite:github.com shopify shopify-app-js for Shopify Node.js librariessite:shopify.dev session storage for session management optionsShopify Admin (iframe)
↓ App Bridge session token
Remix Server (@shopify/shopify-app-remix)
↓ GraphQL Admin API calls
Shopify APIs
↓ Webhooks
Your Webhook Handler
| Package | Purpose |
|---|---|
@shopify/shopify-app-remix | Remix integration (auth, session, billing, webhooks) |
@shopify/shopify-api | Low-level Shopify API client |
@shopify/app-bridge-react | App Bridge React components |
@shopify/polaris | Admin UI components |
shopify-app/
├── app/
│ ├── routes/
│ │ ├── app._index.tsx # App dashboard
│ │ ├── app.products.tsx # Products page
│ │ ├── auth.$.tsx # OAuth callback handler
│ │ └── webhooks.tsx # Webhook endpoint
│ ├── shopify.server.ts # Shopify API client initialization
│ ├── db.server.ts # Database connection
│ └── root.tsx # Root layout
├── prisma/
│ └── schema.prisma # Database schema (session storage)
├── extensions/ # App extensions (Functions, checkout UI)
├── shopify.app.toml # App configuration
├── .env
└── package.json
// app/shopify.server.ts
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.SCOPES?.split(","),
authPathPrefix: "/auth",
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
webhooks: {
APP_UNINSTALLED: {
deliveryMethod: "http",
callbackUrl: "/webhooks",
},
},
hooks: {
afterAuth: async ({ session }) => {
// Register webhooks after successful auth
shopify.registerWebhooks({ session });
},
},
});
export default shopify;
export const apiVersion = shopify.apiVersion;
export const addDocumentResponseHeaders = shopify.addDocumentResponseHeaders;
export const authenticate = shopify.authenticate;
export const registerWebhooks = shopify.registerWebhooks;
// app/routes/app._index.tsx
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";
export async function loader({ request }: LoaderFunctionArgs) {
const { admin } = await authenticate.admin(request);
const response = await admin.graphql(`
query {
products(first: 10) {
edges {
node {
id
title
}
}
}
}
`);
const { data } = await response.json();
return json({ products: data.products.edges });
}
// app/routes/webhooks.tsx
import type { ActionFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";
export async function action({ request }: ActionFunctionArgs) {
const { topic, shop, payload } = await authenticate.webhook(request);
switch (topic) {
case "APP_UNINSTALLED":
// Clean up shop data
await deleteShopData(shop);
break;
case "CUSTOMERS_DATA_REQUEST":
// Handle GDPR data request
break;
case "CUSTOMERS_REDACT":
// Delete customer data
break;
case "SHOP_REDACT":
// Delete all shop data
break;
}
return new Response();
}
| Storage | Package | Use Case |
|---|---|---|
| Prisma | @shopify/shopify-app-session-storage-prisma | Production (SQL databases) |
| SQLite | @shopify/shopify-app-session-storage-sqlite | Development, small apps |
| Redis | @shopify/shopify-app-session-storage-redis | High-traffic apps |
| Memory | @shopify/shopify-app-session-storage-memory | Testing only |
| DynamoDB | @shopify/shopify-app-session-storage-dynamodb | AWS deployments |
The authenticated admin client handles:
// Make a GraphQL call
const response = await admin.graphql(`
mutation ProductCreate($input: ProductInput!) {
productCreate(input: $input) {
product { id title }
userErrors { field message }
}
}
`, {
variables: {
input: { title: "New Product" },
},
});
For endpoints not yet available in GraphQL:
const response = await admin.rest.get({
path: 'themes',
});
Note: REST is deprecated. Use GraphQL whenever possible.
For apps using shopify app deploy:
| Platform | Setup |
|---|---|
| Vercel | vercel deploy |
| Fly.io | fly deploy |
| Railway | railway deploy |
| Render | Git push |
| AWS (ECS/Lambda) | SAM/CDK |
SHOPIFY_API_KEY=your-api-key
SHOPIFY_API_SECRET=your-api-secret
SHOPIFY_APP_URL=https://your-app.example.com
SCOPES=read_products,write_products,read_orders
DATABASE_URL=file:./dev.db
@shopify/shopify-app-remix — do not build auth/session from scratchauthenticate.admin(request)afterAuth hook — they need valid access tokensuserErrors in GraphQL mutations — 200 status does not mean success.envshopify app dev for local development (handles tunneling and auth)Fetch the Shopify Remix app documentation and @shopify/shopify-app-remix package docs for exact initialization, authentication, and deployment patterns before implementing.