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-commerceThis skill is limited to using the following tools:
**Fetch live docs**:
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
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.