Claude
Skills
Sign in
Back

shopify-apps

Included with Lifetime
$97 forever

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.

Web Dev

What this skill does


# 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">
              <Text as="h2" variant="headingMd">
                Welcome to {shop.name}!
              </Text>
              <Text as="p" variant="bodyMd">
                Your app is now connected to this store.
              </Text>
              <Button variant="primary">
                Get Started
              </Button>
            </BlockStack>
          </Card>
        </Layout.Section>
      </Layout>
    </Page>
  );
}

### Notes

- App Bridge required for Built for Shopify (July 2025)
- Polaris components match Shopify Admin design
- TitleBar and navigation from App Bridge
- Always authenticate requests with authenticate.admin()

### Webhook Handling

Secure webhook processing with HMAC verification

**When to use**: Receiving Shopify webhooks

### Template

// app/routes/webhooks.tsx
import type { ActionFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import db from "../db.server";

export const action = async ({ request }: ActionFunctionArgs) => {
  // Authenticate webhook (verifies HMAC signature)
  const { topic, shop, payload, admin } = await authenticate.webhook(request);

  console.log(`Received ${topic} webhook for ${shop}`);

  // Process based on topic
  switch (topic) {
    case "ORDERS_CREATE":
      // Queue for async processing
      await queueOrderProcessing(payload);
      break;

    case "PRODUCTS_UPDATE":
      await handleProductUpdate(shop, payload);
      break;

    case "APP_UNINSTALLED":
      // Clean up shop data
      await db.session.deleteMany({ where: { shop } });
      await db.shopData.delete({ where: { shop } });
      break;

    case "CUSTOMERS_DATA_REQUEST":
    case "CUSTOMERS_REDACT":
    case "SHOP_REDACT":
      // GDPR webhooks - mandatory
      await handleGDPRWebhook(topic, payload);
      break;

    default:
      console.log(`Unhandled webhook topic: ${topic}`);
  }

  // CRITICAL: Return 200 immediately
  // Shopify expects response within 5 seconds
  return new Response(null, { status: 200 });
};

// Process asynchronously after responding
async function queueOrderProcessing(payload: any) {
  // Use a job queue (BullMQ, etc.)
  await jobQueue.add("process-order", {
    orderId: payload.id,
    orderData: payload,
  });
}

async function handleProductUpdate(shop: string, payload: any) {
  // Quick sync operation only
  await db.product.upsert({
    where: { shopifyId: payload.id },
    update: {
      title: payload.title,
      updatedAt: new Date(),
    },
    create: {
      shopifyId: payload.id,
      shop,
      title: payload.title,
    },
  });
}

async function handleGDPRWebhook(topic: string, payload: any) {
  // GDPR compliance - required for all apps
  switch (topic) {
    case "CUSTOMERS_DATA_REQUEST":
      // Return customer data within 30 days
      break;
    case "CUSTOMERS_REDACT":
      // Delete customer data
      break;
    case "SHOP_REDACT":
      // Delete all shop data (48 hours after uninstall)
      break;
  }
}

### Notes

- Respond within 5 seconds or webhook fails
- Use job queues for heavy processing
- GDPR webhooks are mandatory for App Store
- HMAC verification handled by authenticate.webhook()

### GraphQL Admin API

Query and mutate shop data with GraphQL

**When to use**: Interacting with Shopify Admin API

### Template

// GraphQL quer

Related in Web Dev