
About
Expert patterns for Shopify app development including Remix/React
name: shopify-apps description: Expert patterns for Shopify app development including Remix/React Router apps, embedded apps with App Bridge, webhook handling, GraphQL Admin API, Polaris components, billing, and app extensions. risk: safe source: vibeship-spawner-skills (Apache 2.0) date_added: 2026-02-27
Shopify Apps
Expert patterns for Shopify app development including Remix/React Router apps, embedded apps with App Bridge, webhook handling, GraphQL Admin API, Polaris components, billing, and app extensions.
Patterns
React Router App Setup
Modern Shopify app template with React Router
When to use: Starting a new Shopify app
Template
Create new Shopify app with CLI
npm init @shopify/app@latest my-shopify-app
Project structure
my-shopify-app/
├── app/
│ ├── routes/
│ │ ├── app._index.tsx # Main app page
│ │ ├── app.tsx # App layout with providers
│ │ ├── auth.$.tsx # Auth callback
│ │ └── webhooks.tsx # Webhook handler
│ ├── shopify.server.ts # Server configuration
│ └── root.tsx # Root layout
├── extensions/ # App extensions
├── shopify.app.toml # App configuration
└── package.json
// shopify.app.toml name = "my-shopify-app" client_id = "your-client-id" application_url = "https://your-app.example.com"
[access_scopes] scopes = "read_products,write_products,read_orders"
[webhooks] api_version = "2024-10"
[webhooks.subscriptions] topics = ["orders/create", "products/update"] uri = "/webhooks"
[auth] redirect_urls = ["https://your-app.example.com/auth/callback"]
// app/shopify.server.ts import "@shopify/shopify-app-remix/adapters/node"; import { LATEST_API_VERSION, shopifyApp, DeliveryMethod, } 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!, scopes: process.env.SCOPES?.split(","), appUrl: process.env.SHOPIFY_APP_URL!, authPathPrefix: "/auth", sessionStorage: new PrismaSessionStorage(prisma), distribution: AppDistribution.AppStore, future: { unstable_newEmbeddedAuthStrategy: true, }, ...(process.env.SHOP_CUSTOM_DOMAIN ? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] } : {}), });
export default shopify; export const apiVersion = LATEST_API_VERSION; export const authenticate = shopify.authenticate; export const sessionStorage = shopify.sessionStorage;
Notes
- React Router replaced Remix as recommended template (late 2024)
- unstable_newEmbeddedAuthStrategy enabled by default for new apps
- Webhooks configured in shopify.app.toml, not code
- Run 'shopify app deploy' to apply configuration changes
Embedded App with App Bridge
Render app embedded in Shopify Admin
When to use: Building embedded admin app
Template
// app/routes/app.tsx - App layout with providers import { Link, Outlet, useLoaderData, useRouteError } from "@remix-run/react"; import { AppProvider } from "@shopify/shopify-app-remix/react"; import polarisStyles from "@shopify/polaris/build/esm/styles.css?url";
export const links = () => [{ rel: "stylesheet", href: polarisStyles }];
export async function loader({ request }: LoaderFunctionArgs) { await authenticate.admin(request); return json({ apiKey: process.env.SHOPIFY_API_KEY! }); }
export default function App() { const { apiKey } = useLoaderData<typeof loader>();
return ( <AppProvider isEmbeddedApp apiKey={apiKey}> <ui-nav-menu> <Link to="/app" rel="home">Home</Link> <Link to="/app/products">Products</Link> <Link to="/app/settings">Settings</Link> </ui-nav-menu> <Outlet /> </AppProvider> ); }
export function ErrorBoundary() { const error = useRouteError(); return ( <AppProvider isEmbeddedApp> <Page> <Card> <Text as="p" variant="bodyMd"> Something went wrong. Please try again. </Text> </Card> </Page> </AppProvider> ); }
// app/routes/app._index.tsx - Main app page import { Page, Layout, Card, Text, BlockStack, Button, } from "@shopify/polaris"; import { TitleBar } from "@shopify/app-bridge-react";
export async function loader({ request }: LoaderFunctionArgs) { const { admin } = await authenticate.admin(request);
// GraphQL query
const response = await admin.graphql( query { shop { name email } } );
const { data } = await response.json(); return json({ shop: data.shop }); }
export default function Index() { const { shop } = useLoaderData<typeof loader>();
return ( <Page> <TitleBar title="My Shopify App" /> <Layout> <Layout.Section> <Card> <BlockStack gap="200">
