{"id":"77f495fd-4b99-47bf-8acf-ad45317ef974","shortId":"CuHgvb","kind":"skill","title":"Shopify Apps","tagline":"Antigravity Awesome Skills skill by Sickn33","description":"# Shopify Apps\n\nExpert patterns for Shopify app development including Remix/React Router apps,\nembedded apps with App Bridge, webhook handling, GraphQL Admin API,\nPolaris components, billing, and app extensions.\n\n## Patterns\n\n### React Router App Setup\n\nModern Shopify app template with React Router\n\n**When to use**: Starting a new Shopify app\n\n### Template\n\n# Create new Shopify app with CLI\nnpm init @shopify/app@latest my-shopify-app\n\n# Project structure\n# my-shopify-app/\n# ├── app/\n# │   ├── routes/\n# │   │   ├── app._index.tsx        # Main app page\n# │   │   ├── app.tsx               # App layout with providers\n# │   │   ├── auth.$.tsx            # Auth callback\n# │   │   └── webhooks.tsx          # Webhook handler\n# │   ├── shopify.server.ts         # Server configuration\n# │   └── root.tsx                  # Root layout\n# ├── extensions/                   # App extensions\n# ├── shopify.app.toml              # App configuration\n# └── package.json\n\n// shopify.app.toml\nname = \"my-shopify-app\"\nclient_id = \"your-client-id\"\napplication_url = \"https://your-app.example.com\"\n\n[access_scopes]\nscopes = \"read_products,write_products,read_orders\"\n\n[webhooks]\napi_version = \"2024-10\"\n\n[webhooks.subscriptions]\ntopics = [\"orders/create\", \"products/update\"]\nuri = \"/webhooks\"\n\n[auth]\nredirect_urls = [\"https://your-app.example.com/auth/callback\"]\n\n// app/shopify.server.ts\nimport \"@shopify/shopify-app-remix/adapters/node\";\nimport {\n  LATEST_API_VERSION,\n  shopifyApp,\n  DeliveryMethod,\n} from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\nimport prisma from \"./db.server\";\n\nconst shopify = shopifyApp({\n  apiKey: process.env.SHOPIFY_API_KEY!,\n  apiSecretKey: process.env.SHOPIFY_API_SECRET!,\n  scopes: process.env.SCOPES?.split(\",\"),\n  appUrl: process.env.SHOPIFY_APP_URL!,\n  authPathPrefix: \"/auth\",\n  sessionStorage: new PrismaSessionStorage(prisma),\n  distribution: AppDistribution.AppStore,\n  future: {\n    unstable_newEmbeddedAuthStrategy: true,\n  },\n  ...(process.env.SHOP_CUSTOM_DOMAIN\n    ? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] }\n    : {}),\n});\n\nexport default shopify;\nexport const apiVersion = LATEST_API_VERSION;\nexport const authenticate = shopify.authenticate;\nexport const sessionStorage = shopify.sessionStorage;\n\n### Notes\n\n- React Router replaced Remix as recommended template (late 2024)\n- unstable_newEmbeddedAuthStrategy enabled by default for new apps\n- Webhooks configured in shopify.app.toml, not code\n- Run 'shopify app deploy' to apply configuration changes\n\n### Embedded App with App Bridge\n\nRender app embedded in Shopify Admin\n\n**When to use**: Building embedded admin app\n\n### Template\n\n// app/routes/app.tsx - App layout with providers\nimport { Link, Outlet, useLoaderData, useRouteError } from \"@remix-run/react\";\nimport { AppProvider } from \"@shopify/shopify-app-remix/react\";\nimport polarisStyles from \"@shopify/polaris/build/esm/styles.css?url\";\n\nexport const links = () => [{ rel: \"stylesheet\", href: polarisStyles }];\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  await authenticate.admin(request);\n  return json({ apiKey: process.env.SHOPIFY_API_KEY! });\n}\n\nexport default function App() {\n  const { apiKey } = useLoaderData<typeof loader>();\n\n  return (\n    <AppProvider isEmbeddedApp apiKey={apiKey}>\n      <ui-nav-menu>\n        <Link to=\"/app\" rel=\"home\">Home</Link>\n        <Link to=\"/app/products\">Products</Link>\n        <Link to=\"/app/settings\">Settings</Link>\n      </ui-nav-menu>\n      <Outlet />\n    </AppProvider>\n  );\n}\n\nexport function ErrorBoundary() {\n  const error = useRouteError();\n  return (\n    <AppProvider isEmbeddedApp>\n      <Page>\n        <Card>\n          <Text as=\"p\" variant=\"bodyMd\">\n            Something went wrong. Please try again.\n          </Text>\n        </Card>\n      </Page>\n    </AppProvider>\n  );\n}\n\n// app/routes/app._index.tsx - Main app page\nimport {\n  Page,\n  Layout,\n  Card,\n  Text,\n  BlockStack,\n  Button,\n} from \"@shopify/polaris\";\nimport { TitleBar } from \"@shopify/app-bridge-react\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const { admin } = await authenticate.admin(request);\n\n  // GraphQL query\n  const response = await admin.graphql(`\n    query {\n      shop {\n        name\n        email\n      }\n    }\n  `);\n\n  const { data } = await response.json();\n  return json({ shop: data.shop });\n}\n\nexport default function Index() {\n  const { shop } = useLoaderData<typeof loader>();\n\n  return (\n    <Page>\n      <TitleBar title=\"My Shopify App\" />\n      <Layout>\n        <Layout.Section>\n          <Card>\n            <BlockStack gap=\"200\">\n              <Text as=\"h2\" variant=\"headingMd\">\n                Welcome to {shop.name}!\n              </Text>\n              <Text as=\"p\" variant=\"bodyMd\">\n                Your app is now connected to this store.\n              </Text>\n              <Button variant=\"primary\">\n                Get Started\n              </Button>\n            </BlockStack>\n          </Card>\n        </Layout.Section>\n      </Layout>\n    </Page>\n  );\n}\n\n### Notes\n\n- App Bridge required for Built for Shopify (July 2025)\n- Polaris components match Shopify Admin design\n- TitleBar and navigation from App Bridge\n- Always authenticate requests with authenticate.admin()\n\n### Webhook Handling\n\nSecure webhook processing with HMAC verification\n\n**When to use**: Receiving Shopify webhooks\n\n### Template\n\n// app/routes/webhooks.tsx\nimport type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport db from \"../db.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n  // Authenticate webhook (verifies HMAC signature)\n  const { topic, shop, payload, admin } = await authenticate.webhook(request);\n\n  console.log(`Received ${topic} webhook for ${shop}`);\n\n  // Process based on topic\n  switch (topic) {\n    case \"ORDERS_CREATE\":\n      // Queue for async processing\n      await queueOrderProcessing(payload);\n      break;\n\n    case \"PRODUCTS_UPDATE\":\n      await handleProductUpdate(shop, payload);\n      break;\n\n    case \"APP_UNINSTALLED\":\n      // Clean up shop data\n      await db.session.deleteMany({ where: { shop } });\n      await db.shopData.delete({ where: { shop } });\n      break;\n\n    case \"CUSTOMERS_DATA_REQUEST\":\n    case \"CUSTOMERS_REDACT\":\n    case \"SHOP_REDACT\":\n      // GDPR webhooks - mandatory\n      await handleGDPRWebhook(topic, payload);\n      break;\n\n    default:\n      console.log(`Unhandled webhook topic: ${topic}`);\n  }\n\n  // CRITICAL: Return 200 immediately\n  // Shopify expects response within 5 seconds\n  return new Response(null, { status: 200 });\n};\n\n// Process asynchronously after responding\nasync function queueOrderProcessing(payload: any) {\n  // Use a job queue (BullMQ, etc.)\n  await jobQueue.add(\"process-order\", {\n    orderId: payload.id,\n    orderData: payload,\n  });\n}\n\nasync function handleProductUpdate(shop: string, payload: any) {\n  // Quick sync operation only\n  await db.product.upsert({\n    where: { shopifyId: payload.id },\n    update: {\n      title: payload.title,\n      updatedAt: new Date(),\n    },\n    create: {\n      shopifyId: payload.id,\n      shop,\n      title: payload.title,\n    },\n  });\n}\n\nasync function handleGDPRWebhook(topic: string, payload: any) {\n  // GDPR compliance - required for all apps\n  switch (topic) {\n    case \"CUSTOMERS_DATA_REQUEST\":\n      // Return customer data within 30 days\n      break;\n    case \"CUSTOMERS_REDACT\":\n      // Delete customer data\n      break;\n    case \"SHOP_REDACT\":\n      // Delete all shop data (48 hours after uninstall)\n      break;\n  }\n}\n\n### Notes\n\n- Respond within 5 seconds or webhook fails\n- Use job queues for heavy processing\n- GDPR webhooks are mandatory for App Store\n- HMAC verification handled by authenticate.webhook()\n\n### GraphQL Admin API\n\nQuery and mutate shop data with GraphQL\n\n**When to use**: Interacting with Shopify Admin API\n\n### Template\n\n// GraphQL queries with authenticated admin client\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const { admin } = await authenticate.admin(request);\n\n  // Query products with pagination\n  const response = await admin.graphql(`\n    query GetProducts($first: Int!, $after: String) {\n      products(first: $first, after: $after) {\n        edges {\n          node {\n            id\n            title\n            status\n            totalInventory\n            priceRangeV2 {\n              minVariantPrice {\n                amount\n                currencyCode\n              }\n            }\n            images(first: 1) {\n              edges {\n                node {\n                  url\n                  altText\n                }\n              }\n            }\n          }\n          cursor\n        }\n        pageInfo {\n          hasNextPage\n          endCursor\n        }\n      }\n    }\n  `, {\n    variables: {\n      first: 10,\n      after: null,\n    },\n  });\n\n  const { data } = await response.json();\n  return json({ products: data.products });\n}\n\n// Mutations\nexport async function action({ request }: ActionFunctionArgs) {\n  const { admin } = await authenticate.admin(request);\n  const formData = await request.formData();\n  const productId = formData.get(\"productId\");\n  const newTitle = formData.get(\"title\");\n\n  const response = await admin.graphql(`\n    mutation UpdateProduct($input: ProductInput!) {\n      productUpdate(input: $input) {\n        product {\n          id\n          title\n        }\n        userErrors {\n          field\n          message\n        }\n      }\n    }\n  `, {\n    variables: {\n      input: {\n        id: productId,\n        title: newTitle,\n      },\n    },\n  });\n\n  const { data } = await response.json();\n\n  if (data.productUpdate.userErrors.length > 0) {\n    return json({\n      errors: data.productUpdate.userErrors,\n    }, { status: 400 });\n  }\n\n  return json({ product: data.productUpdate.product });\n}\n\n// Bulk operations for large datasets\nasync function bulkUpdateProducts(admin: AdminApiContext) {\n  // Create bulk operation\n  const response = await admin.graphql(`\n    mutation {\n      bulkOperationRunMutation(\n        mutation: \"mutation call($input: ProductInput!) {\n          productUpdate(input: $input) { product { id } }\n        }\",\n        stagedUploadPath: \"path-to-staged-upload\"\n      ) {\n        bulkOperation {\n          id\n          status\n        }\n        userErrors {\n          message\n        }\n      }\n    }\n  `);\n\n  // Poll for completion or use webhook\n  // BULK_OPERATIONS_FINISH webhook\n}\n\n### Notes\n\n- GraphQL required for new public apps (April 2025)\n- Rate limit: 1000 points per 60 seconds\n- Use bulk operations for >250 items\n- Direct API access available from App Bridge\n\n### Billing API Integration\n\nImplement subscription billing for your app\n\n**When to use**: Monetizing Shopify app\n\n### Template\n\n// app/routes/app.billing.tsx\nimport { json, redirect } from \"@remix-run/node\";\nimport { Page, Card, Button, BlockStack, Text } from \"@shopify/polaris\";\nimport { authenticate } from \"../shopify.server\";\n\nconst PLANS = {\n  basic: {\n    name: \"Basic\",\n    amount: 9.99,\n    currencyCode: \"USD\",\n    interval: \"EVERY_30_DAYS\",\n  },\n  pro: {\n    name: \"Pro\",\n    amount: 29.99,\n    currencyCode: \"USD\",\n    interval: \"EVERY_30_DAYS\",\n  },\n};\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const { admin, billing } = await authenticate.admin(request);\n\n  // Check current subscription\n  const response = await admin.graphql(`\n    query {\n      currentAppInstallation {\n        activeSubscriptions {\n          id\n          name\n          status\n          lineItems {\n            plan {\n              pricingDetails {\n                ... on AppRecurringPricing {\n                  price {\n                    amount\n                    currencyCode\n                  }\n                  interval\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  `);\n\n  const { data } = await response.json();\n  return json({\n    subscription: data.currentAppInstallation.activeSubscriptions[0],\n  });\n}\n\nexport async function action({ request }: ActionFunctionArgs) {\n  const { admin, session } = await authenticate.admin(request);\n  const formData = await request.formData();\n  const planKey = formData.get(\"plan\") as keyof typeof PLANS;\n  const plan = PLANS[planKey];\n\n  // Create subscription charge\n  const response = await admin.graphql(`\n    mutation CreateSubscription($name: String!, $lineItems: [AppSubscriptionLineItemInput!]!, $returnUrl: URL!, $test: Boolean) {\n      appSubscriptionCreate(\n        name: $name\n        lineItems: $lineItems\n        returnUrl: $returnUrl\n        test: $test\n      ) {\n        appSubscription {\n          id\n          status\n        }\n        confirmationUrl\n        userErrors {\n          field\n          message\n        }\n      }\n    }\n  `, {\n    variables: {\n      name: plan.name,\n      lineItems: [\n        {\n          plan: {\n            appRecurringPricingDetails: {\n              price: {\n                amount: plan.amount,\n                currencyCode: plan.currencyCode,\n              },\n              interval: plan.interval,\n            },\n          },\n        },\n      ],\n      returnUrl: `https://${session.shop}/admin/apps/${process.env.SHOPIFY_API_KEY}`,\n      test: process.env.NODE_ENV !== \"production\",\n    },\n  });\n\n  const { data } = await response.json();\n\n  if (data.appSubscriptionCreate.userErrors.length > 0) {\n    return json({\n      errors: data.appSubscriptionCreate.userErrors,\n    }, { status: 400 });\n  }\n\n  // Redirect merchant to approve charge\n  return redirect(data.appSubscriptionCreate.confirmationUrl);\n}\n\nexport default function Billing() {\n  const { subscription } = useLoaderData<typeof loader>();\n  const submit = useSubmit();\n\n  return (\n    <Page title=\"Billing\">\n      <Card>\n        {subscription ? (\n          <BlockStack gap=\"200\">\n            <Text as=\"p\" variant=\"bodyMd\">\n              Current plan: {subscription.name}\n            </Text>\n            <Text as=\"p\" variant=\"bodyMd\">\n              Status: {subscription.status}\n            </Text>\n          </BlockStack>\n        ) : (\n          <BlockStack gap=\"400\">\n            <Text as=\"h2\" variant=\"headingMd\">\n              Choose a Plan\n            </Text>\n            <Button onClick={() => submit({ plan: \"basic\" }, { method: \"post\" })}>\n              Basic - $9.99/month\n            </Button>\n            <Button onClick={() => submit({ plan: \"pro\" }, { method: \"post\" })}>\n              Pro - $29.99/month\n            </Button>\n          </BlockStack>\n        )}\n      </Card>\n    </Page>\n  );\n}\n\n### Notes\n\n- Use test: true for development stores\n- Merchant must approve subscription\n- One recurring + one usage charge per app max\n- 30-day billing cycle for recurring charges\n\n### App Extension Development\n\nExtend Shopify checkout, admin, or storefront\n\n**When to use**: Building app extensions\n\n### Template\n\n# shopify.extension.toml (in extensions/my-extension/)\napi_version = \"2024-10\"\n\n[[extensions]]\ntype = \"ui_extension\"\nname = \"Product Customizer\"\nhandle = \"product-customizer\"\n\n[[extensions.targeting]]\ntarget = \"admin.product-details.block.render\"\nmodule = \"./src/AdminBlock.tsx\"\n\n[extensions.capabilities]\napi_access = true\n\n[extensions.settings]\n[[extensions.settings.fields]]\nkey = \"show_preview\"\ntype = \"boolean\"\nname = \"Show Preview\"\n\n// extensions/my-extension/src/AdminBlock.tsx\nimport {\n  reactExtension,\n  useApi,\n  useSettings,\n  BlockStack,\n  Text,\n  Button,\n  InlineStack,\n} from \"@shopify/ui-extensions-react/admin\";\n\nexport default reactExtension(\n  \"admin.product-details.block.render\",\n  () => <ProductCustomizer />\n);\n\nfunction ProductCustomizer() {\n  const { data, extension } = useApi<\"admin.product-details.block.render\">();\n  const settings = useSettings();\n\n  const productId = data?.selected?.[0]?.id;\n\n  const handleCustomize = async () => {\n    // API calls from extension\n    const result = await fetch(\"/api/customize\", {\n      method: \"POST\",\n      body: JSON.stringify({ productId }),\n    });\n  };\n\n  return (\n    <BlockStack gap=\"base\">\n      <Text fontWeight=\"bold\">Product Customizer</Text>\n      <Text>\n        Customize product: {productId}\n      </Text>\n      {settings.show_preview && (\n        <Text size=\"small\">Preview enabled</Text>\n      )}\n      <InlineStack gap=\"base\">\n        <Button onPress={handleCustomize}>\n          Apply Customization\n        </Button>\n      </InlineStack>\n    </BlockStack>\n  );\n}\n\n// Checkout UI Extension\n// [[extensions.targeting]]\n// target = \"purchase.checkout.block.render\"\n\n// extensions/checkout-ext/src/Checkout.tsx\nimport {\n  reactExtension,\n  Banner,\n  useCartLines,\n  useTotalAmount,\n} from \"@shopify/ui-extensions-react/checkout\";\n\nexport default reactExtension(\n  \"purchase.checkout.block.render\",\n  () => <CheckoutBanner />\n);\n\nfunction CheckoutBanner() {\n  const cartLines = useCartLines();\n  const total = useTotalAmount();\n\n  if (total.amount > 100) {\n    return (\n      <Banner status=\"success\">\n        You qualify for free shipping!\n      </Banner>\n    );\n  }\n\n  return null;\n}\n\n### Notes\n\n- Extensions run in sandboxed iframe\n- Use @shopify/ui-extensions-react for React\n- Limited APIs compared to full app\n- Deploy with 'shopify app deploy'\n\n## Sharp Edges\n\n### Webhook Must Respond Within 5 Seconds\n\nSeverity: HIGH\n\nSituation: Receiving webhooks from Shopify\n\nSymptoms:\nWebhook deliveries marked as failed.\n\"Your app didn't respond in time\" in Shopify logs.\nMissing order/product updates.\nWebhooks retried repeatedly then cancelled.\n\nWhy this breaks:\nShopify expects a 2xx response within 5 seconds. If your app processes\nthe webhook data before responding, you'll timeout.\n\nShopify retries failed webhooks up to 19 times over 48 hours.\nAfter continued failures, webhooks may be cancelled entirely.\n\nHeavy processing (API calls, database operations) must happen\nafter the response is sent.\n\nRecommended fix:\n\n## Respond immediately, process asynchronously\n\n```typescript\n// app/routes/webhooks.tsx\nexport const action = async ({ request }: ActionFunctionArgs) => {\n  const { topic, shop, payload } = await authenticate.webhook(request);\n\n  // Queue for async processing\n  await jobQueue.add(\"process-webhook\", {\n    topic,\n    shop,\n    payload,\n  });\n\n  // CRITICAL: Return 200 immediately\n  return new Response(null, { status: 200 });\n};\n\n// Worker process handles the actual work\n// workers/webhook-processor.ts\nimport { Worker } from \"bullmq\";\n\nconst worker = new Worker(\"process-webhook\", async (job) => {\n  const { topic, shop, payload } = job.data;\n\n  switch (topic) {\n    case \"ORDERS_CREATE\":\n      await processOrder(shop, payload);\n      break;\n    // ... other handlers\n  }\n});\n```\n\n## For simple operations, be quick\n\n```typescript\n// Simple database update is OK if fast\nexport const action = async ({ request }: ActionFunctionArgs) => {\n  const { topic, payload } = await authenticate.webhook(request);\n\n  // Quick database update (< 1 second)\n  await db.product.update({\n    where: { shopifyId: payload.id },\n    data: { title: payload.title },\n  });\n\n  return new Response(null, { status: 200 });\n};\n```\n\n## Monitor webhook performance\n\n```typescript\n// Log response times\nconst start = Date.now();\n\nawait handleWebhook(payload);\n\nconst duration = Date.now() - start;\nconsole.log(`Webhook processed in ${duration}ms`);\n\n// Alert if approaching timeout\nif (duration > 3000) {\n  console.warn(\"Webhook processing taking too long!\");\n}\n```\n\n### API Rate Limits Cause 429 Errors\n\nSeverity: HIGH\n\nSituation: Making API calls to Shopify\n\nSymptoms:\nHTTP 429 Too Many Requests errors.\n\"Throttled\" responses.\nApp becomes unresponsive.\nOperations fail silently or partially.\n\nWhy this breaks:\nShopify enforces strict rate limits:\n- REST: 2 requests per second per store\n- GraphQL: 1000 points per 60 seconds\n\nExceeding limits causes immediate 429 errors.\nContinuous violations can result in temporary bans.\n\nBulk operations count against limits.\n\nRecommended fix:\n\n## Check rate limit headers\n\n```typescript\n// REST API\n// X-Shopify-Shop-Api-Call-Limit: 39/40\n\n// GraphQL - check response extensions\nconst response = await admin.graphql(`...`);\nconst { data, extensions } = await response.json();\n\nconst cost = extensions?.cost;\n// {\n//   \"requestedQueryCost\": 42,\n//   \"actualQueryCost\": 42,\n//   \"throttleStatus\": {\n//     \"maximumAvailable\": 1000,\n//     \"currentlyAvailable\": 958,\n//     \"restoreRate\": 50\n//   }\n// }\n```\n\n## Implement retry with exponential backoff\n\n```typescript\nasync function shopifyRequest(\n  fn: () => Promise<Response>,\n  maxRetries = 3\n): Promise<Response> {\n  let lastError: Error;\n\n  for (let attempt = 0; attempt < maxRetries; attempt++) {\n    try {\n      const response = await fn();\n\n      if (response.status === 429) {\n        // Get retry-after header or default\n        const retryAfter = parseInt(\n          response.headers.get(\"Retry-After\") || \"2\"\n        );\n        await sleep(retryAfter * 1000 * Math.pow(2, attempt));\n        continue;\n      }\n\n      return response;\n    } catch (error) {\n      lastError = error as Error;\n    }\n  }\n\n  throw lastError!;\n}\n```\n\n## Use bulk operations for large datasets\n\n```typescript\n// Instead of 1000 individual calls, use bulk mutation\nconst response = await admin.graphql(`\n  mutation {\n    bulkOperationRunMutation(\n      mutation: \"mutation($input: ProductInput!) {\n        productUpdate(input: $input) { product { id } }\n      }\",\n      stagedUploadPath: \"...\"\n    ) {\n      bulkOperation { id status }\n      userErrors { message }\n    }\n  }\n`);\n```\n\n## Queue requests\n\n```typescript\nimport { RateLimiter } from \"limiter\";\n\n// 2 requests per second for REST\nconst limiter = new RateLimiter({\n  tokensPerInterval: 2,\n  interval: \"second\",\n});\n\nasync function rateLimitedRequest(fn: () => Promise<any>) {\n  await limiter.removeTokens(1);\n  return fn();\n}\n```\n\n### Protected Customer Data Requires Special Permission\n\nSeverity: HIGH\n\nSituation: Accessing customer PII in webhooks or API\n\nSymptoms:\nWebhook deliveries fail for orders/customers.\nCustomer data fields are null or empty.\nApp works in development but fails in production.\n\"Protected customer data access\" errors.\n\nWhy this breaks:\nSince April 2024, accessing protected customer data (PII) requires\nexplicit approval from Shopify. This is separate from OAuth scopes.\n\nProtected data includes:\n- Customer names, emails, addresses\n- Order customer information\n- Subscription customer details\n\nEven with read_orders scope, you won't receive customer data\nin webhooks without protected data access.\n\nRecommended fix:\n\n## Request protected customer data access\n\n1. Go to Partner Dashboard > App > API access\n2. Under \"Protected customer data access\"\n3. Request access for needed data types\n4. Justify your use case\n5. Wait for Shopify approval (can take days)\n\n## Check your data access level\n\n```typescript\n// Query your app's data access\nconst response = await admin.graphql(`\n  query {\n    currentAppInstallation {\n      accessScopes {\n        handle\n      }\n    }\n  }\n`);\n```\n\n## Handle missing data gracefully\n\n```typescript\n// Webhook payload may have redacted fields\nasync function processOrder(payload: any) {\n  const customerEmail = payload.customer?.email;\n\n  if (!customerEmail) {\n    // Customer data not available\n    // Either no protected access or data redacted\n    console.log(\"Customer data not available\");\n    return;\n  }\n\n  await sendOrderConfirmation(customerEmail);\n}\n```\n\n## Use customer account API for direct access\n\n```typescript\n// If customer is logged in, can access their data\n// through Customer Account API (different from Admin API)\n```\n\n### Duplicate Webhook Definitions Cause Conflicts\n\nSeverity: MEDIUM\n\nSituation: Configuring webhooks in both TOML and code\n\nSymptoms:\nDuplicate webhook deliveries.\nSome webhooks fire twice.\nWebhook subscriptions fail to register.\nUnpredictable webhook behavior.\n\nWhy this breaks:\nShopify apps can define webhooks in two places:\n1. shopify.app.toml (declarative, recommended)\n2. afterAuth hook in code (imperative, legacy)\n\nIf you define the same webhook in both places, you get:\n- Duplicate subscriptions\n- Race conditions during registration\n- Conflicts during app updates\n\nRecommended fix:\n\n## Use TOML only (recommended)\n\n```toml\n# shopify.app.toml\n[webhooks]\napi_version = \"2024-10\"\n\n[webhooks.subscriptions]\ntopics = [\n  \"orders/create\",\n  \"orders/updated\",\n  \"products/create\",\n  \"products/update\",\n  \"app/uninstalled\"\n]\nuri = \"/webhooks\"\n```\n\n## Remove code-based registration\n\n```typescript\n// DON'T do this if using TOML\nconst shopify = shopifyApp({\n  // ...\n  hooks: {\n    afterAuth: async ({ session }) => {\n      // Remove webhook registration from here\n      // Let TOML handle it\n    },\n  },\n});\n```\n\n## Deploy to apply TOML changes\n\n```bash\n# Webhooks registered on deploy\nshopify app deploy\n```\n\n## Check current subscriptions\n\n```typescript\nconst response = await admin.graphql(`\n  query {\n    webhookSubscriptions(first: 50) {\n      edges {\n        node {\n          id\n          topic\n          endpoint {\n            ... on WebhookHttpEndpoint {\n              callbackUrl\n            }\n          }\n        }\n      }\n    }\n  }\n`);\n```\n\n### Webhook URL Trailing Slash Causes 404\n\nSeverity: MEDIUM\n\nSituation: Setting up webhook endpoints\n\nSymptoms:\nWebhooks return 404 Not Found.\nWebhook delivery fails immediately.\nWorks in local dev but fails in production.\nLogs show request to /webhooks/ not /webhooks.\n\nWhy this breaks:\nShopify automatically adds a trailing slash to webhook URLs.\nIf your server doesn't handle both /webhooks and /webhooks/,\nthe webhook will 404.\n\nCommon with frameworks that are strict about trailing slashes.\n\nRecommended fix:\n\n## Handle both URL formats\n\n```typescript\n// Remix/React Router - both work by default\n// app/routes/webhooks.tsx handles /webhooks\n\n// Express - add middleware\napp.use((req, res, next) => {\n  if (req.path.endsWith('/') && req.path.length > 1) {\n    const query = req.url.slice(req.path.length);\n    const safePath = req.path.slice(0, -1);\n    res.redirect(301, safePath + query);\n  }\n  next();\n});\n```\n\n## Configure web server\n\n```nginx\n# Nginx - strip trailing slashes\nlocation ~ ^(.+)/$ {\n  return 301 $1;\n}\n\n# Or rewrite to handler\nlocation /webhooks {\n  try_files $uri $uri/ @webhooks;\n}\nlocation @webhooks {\n  proxy_pass http://app:3000/webhooks;\n}\n```\n\n## Test both formats\n\n```bash\n# Test without slash\ncurl -X POST https://your-app.com/webhooks\n\n# Test with slash\ncurl -X POST https://your-app.com/webhooks/\n```\n\n### REST API Required Migration to GraphQL (April 2025)\n\nSeverity: HIGH\n\nSituation: Building new public apps or maintaining existing\n\nSymptoms:\nApp store submission rejected for REST API usage.\nDeprecation warnings in console.\nSome REST endpoints stop working.\nMissing features only in GraphQL.\n\nWhy this breaks:\nAs of October 2024, REST Admin API is legacy.\nStarting April 2025, new public apps MUST use GraphQL.\n\nREST endpoints will continue working for existing apps,\nbut new features are GraphQL-only.\n\nMetafields, bulk operations, and many new features\nrequire GraphQL.\n\nRecommended fix:\n\n## Use GraphQL for all new code\n\n```typescript\n// REST (legacy)\nconst response = await fetch(\n  `https://${shop}/admin/api/2024-10/products.json`,\n  {\n    headers: { \"X-Shopify-Access-Token\": token },\n  }\n);\n\n// GraphQL (recommended)\nconst response = await admin.graphql(`\n  query {\n    products(first: 10) {\n      edges {\n        node {\n          id\n          title\n        }\n      }\n    }\n  }\n`);\n```\n\n## Migrate existing REST calls\n\n```typescript\n// REST: GET /products/{id}.json\n// GraphQL equivalent:\nconst response = await admin.graphql(`\n  query GetProduct($id: ID!) {\n    product(id: $id) {\n      id\n      title\n      status\n      variants(first: 10) {\n        edges {\n          node {\n            id\n            price\n            inventoryQuantity\n          }\n        }\n      }\n    }\n  }\n`, {\n  variables: { id: `gid://shopify/Product/${productId}` },\n});\n```\n\n## Use GraphQL for webhooks too\n\n```toml\n# shopify.app.toml\n[webhooks]\napi_version = \"2024-10\"  # Use latest GraphQL version\n```\n\n### App Bridge Required for Built for Shopify (July 2025)\n\nSeverity: HIGH\n\nSituation: Building embedded Shopify apps\n\nSymptoms:\nApp rejected from \"Built for Shopify\" program.\nApp not appearing correctly in admin.\nNavigation and chrome issues.\nWarning about App Bridge version.\n\nWhy this breaks:\nEffective July 2025, all apps seeking \"Built for Shopify\" status\nmust use the latest version of App Bridge and be embedded.\n\nApps using old App Bridge versions or not embedded will\nlose built for Shopify benefits (better placement, badges).\n\nShopify now serves App Bridge and Polaris via unversioned\nscript tags that auto-update.\n\nRecommended fix:\n\n## Use latest App Bridge via script tag\n\n```html\n<!-- Automatically stays up to date -->\n<script src=\"https://cdn.shopify.com/shopifycloud/app-bridge.js\"></script>\n```\n\n## Use AppProvider in React\n\n```typescript\n// app/routes/app.tsx\nimport { AppProvider } from \"@shopify/shopify-app-remix/react\";\n\nexport default function App() {\n  return (\n    <AppProvider isEmbeddedApp apiKey={apiKey}>\n      <Outlet />\n    </AppProvider>\n  );\n}\n```\n\n## Enable embedded auth strategy\n\n```typescript\n// shopify.server.ts\nconst shopify = shopifyApp({\n  // ...\n  future: {\n    unstable_newEmbeddedAuthStrategy: true,\n  },\n});\n```\n\n## Check embedded status\n\n```typescript\nimport { useAppBridge } from \"@shopify/app-bridge-react\";\n\nfunction MyComponent() {\n  const app = useAppBridge();\n  const isEmbedded = app.hostOrigin !== window.location.origin;\n}\n```\n\n### Missing GDPR Webhooks Block App Store Approval\n\nSeverity: HIGH\n\nSituation: Submitting app to Shopify App Store\n\nSymptoms:\nApp submission rejected.\n\"GDPR webhooks not implemented\" error.\nManual review fails for compliance.\nData request webhooks not handled.\n\nWhy this breaks:\nShopify requires all apps to handle three GDPR webhooks:\n1. customers/data_request - Provide customer data\n2. customers/redact - Delete customer data\n3. shop/redact - Delete all shop data\n\nThese are automatically subscribed when you create an app.\nYou MUST implement handlers even if you don't store data.\n\nRecommended fix:\n\n## Implement all GDPR handlers\n\n```typescript\n// app/routes/webhooks.tsx\nexport const action = async ({ request }: ActionFunctionArgs) => {\n  const { topic, payload, shop } = await authenticate.webhook(request);\n\n  switch (topic) {\n    case \"CUSTOMERS_DATA_REQUEST\":\n      await handleDataRequest(shop, payload);\n      break;\n\n    case \"CUSTOMERS_REDACT\":\n      await handleCustomerRedact(shop, payload);\n      break;\n\n    case \"SHOP_REDACT\":\n      await handleShopRedact(shop, payload);\n      break;\n  }\n\n  return new Response(null, { status: 200 });\n};\n\nasync function handleDataRequest(shop: string, payload: any) {\n  const customerId = payload.customer.id;\n\n  // Return customer data within 30 days\n  // Usually send to data_request.destination_url\n  const customerData = await db.customer.findUnique({\n    where: { shopifyId: customerId, shop },\n  });\n\n  if (customerData) {\n    // Send to provided URL or email\n    await sendDataToMerchant(payload.data_request, customerData);\n  }\n}\n\nasync function handleCustomerRedact(shop: string, payload: any) {\n  const customerId = payload.customer.id;\n\n  // Delete customer's personal data\n  await db.customer.deleteMany({\n    where: { shopifyId: customerId, shop },\n  });\n\n  await db.order.updateMany({\n    where: { customerId, shop },\n    data: { customerEmail: null, customerName: null },\n  });\n}\n\nasync function handleShopRedact(shop: string, payload: any) {\n  // Shop uninstalled 48+ hours ago\n  // Delete ALL data for this shop\n  await db.session.deleteMany({ where: { shop } });\n  await db.customer.deleteMany({ where: { shop } });\n  await db.order.deleteMany({ where: { shop } });\n  await db.settings.deleteMany({ where: { shop } });\n}\n```\n\n## Even if you store nothing\n\n```typescript\n// You must still respond 200\ncase \"CUSTOMERS_DATA_REQUEST\":\ncase \"CUSTOMERS_REDACT\":\ncase \"SHOP_REDACT\":\n  // No data stored, but must acknowledge\n  console.log(`GDPR ${topic} for ${shop} - no data stored`);\n  break;\n```\n\n## Validation Checks\n\n### Hardcoded Shopify API Secret\n\nSeverity: ERROR\n\nAPI secrets must never be hardcoded\n\nMessage: Hardcoded Shopify API secret. Use environment variables.\n\n### Hardcoded Shopify API Key\n\nSeverity: ERROR\n\nAPI keys should use environment variables\n\nMessage: Hardcoded Shopify API key. Use environment variables.\n\n### Missing HMAC Verification\n\nSeverity: ERROR\n\nWebhook endpoints must verify HMAC signature\n\nMessage: Webhook handler without HMAC verification. Use authenticate.webhook().\n\n### Synchronous Webhook Processing\n\nSeverity: WARNING\n\nWebhook handlers should respond quickly\n\nMessage: Multiple await calls in webhook handler. Consider async processing.\n\n### Missing Webhook Response\n\nSeverity: ERROR\n\nWebhooks must return 200 status\n\nMessage: Webhook handler may not return proper response.\n\n### Duplicate Webhook Registration\n\nSeverity: WARNING\n\nWebhooks should be defined in TOML only\n\nMessage: Code-based webhook registration. Define webhooks in shopify.app.toml.\n\n### REST API Usage\n\nSeverity: INFO\n\nREST API is deprecated, use GraphQL\n\nMessage: REST API usage detected. Consider migrating to GraphQL.\n\n### Missing Rate Limit Handling\n\nSeverity: WARNING\n\nAPI calls should handle 429 responses\n\nMessage: API call without rate limit handling. Implement retry logic.\n\n### In-Memory Session Storage\n\nSeverity: WARNING\n\nIn-memory sessions don't scale\n\nMessage: In-memory session storage. Use PrismaSessionStorage or similar.\n\n### Missing Session Validation\n\nSeverity: ERROR\n\nRoutes should validate session\n\nMessage: Loader without authentication. Use authenticate.admin(request).\n\n## Collaboration\n\n### Delegation Triggers\n\n- user needs payment processing -> stripe-integration (Shopify Payments or Stripe integration)\n- user needs custom authentication -> auth-specialist (Beyond Shopify OAuth)\n- user needs email/SMS notifications -> twilio-communications (Customer notifications outside Shopify)\n- user needs AI features -> llm-architect (Product descriptions, chatbots)\n- user needs serverless deployment -> aws-serverless (Lambda or Vercel deployment)\n\n## When to Use\n- User mentions or implies: shopify app\n- User mentions or implies: shopify\n- User mentions or implies: embedded app\n- User mentions or implies: polaris\n- User mentions or implies: app bridge\n- User mentions or implies: shopify webhook\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.","tags":["shopify","apps","antigravity","awesome","skills","sickn33"],"capabilities":["skill","source-sickn33","category-antigravity-awesome-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/shopify-apps","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"install_from":"skills.sh"}},"qualityScore":"0.300","qualityRationale":"deterministic score 0.30 from registry signals: · indexed on skills.sh · published under sickn33/antigravity-awesome-skills","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill:v1","enrichmentVersion":1,"enrichedAt":"2026-04-25T09:40:47.538Z","embedding":null,"createdAt":"2026-04-18T20:37:51.333Z","updatedAt":"2026-04-25T09:40:47.538Z","lastSeenAt":"2026-04-25T09:40:47.538Z","tsv":"'-1':2373 '-10':137,1232,2193,2603 '/admin/api/2024-10/products.json':2532 '/admin/apps':1115 '/api/customize':1305 '/auth':188 '/auth/callback':149 '/db.server':168,473 '/month':1173,1183 '/node':465,959 '/products':2561 '/react':288 '/shopify.server':469,971 '/src/adminblock.tsx':1248 '/webhooks':143,2202,2300,2302,2322,2324,2353,2396,2420 '/webhooks/':2429 '0':845,1038,1129,1292,1773,2372 '1':770,1586,1882,1986,2149,2364,2390,2810 '10':781,2549,2582 '100':1354 '1000':917,1685,1748,1803,1827 '19':1452 '2':1678,1799,1805,1861,1872,1994,2153,2815 '200':566,579,1513,1520,1601,2899,3017,3132 '2024':136,232,1231,1932,2192,2477,2602 '2025':424,914,2437,2485,2616,2652 '250':926 '29.99':989,1182 '2xx':1429 '3':1765,2000,2820 '30':655,983,994,1203,2914 '3000':1631 '3000/webhooks':2407 '301':2375,2389 '39/40':1724 '4':2007 '400':851,1135 '404':2270,2281,2328 '42':1743,1745 '429':1642,1654,1694,1784,3194 '48':672,1455,2982 '5':572,680,1390,1432,2012 '50':1752,2256 '60':920,1688 '9.99':978,1172 '958':1750 'access':124,930,1251,1894,1925,1933,1978,1985,1993,1999,2002,2023,2031,2069,2088,2096,2537 'accessscop':2038 'account':2084,2101 'acknowledg':3033 'action':476,796,1042,1488,1573,2856 'actionfunctionarg':460,479,798,1044,1491,1576,2859 'activesubscript':1017 'actual':1525 'actualquerycost':1744 'add':2308,2355 'address':1955 'admin':29,265,271,372,429,489,704,719,726,735,800,864,1003,1046,1216,2105,2479,2637 'admin.graphql':381,746,819,872,1014,1073,1732,1836,2035,2252,2545,2569 'admin.product-details.block.render':1246,1277,1284 'adminapicontext':865 'afterauth':2154,2220 'ago':2984 'ai':3284 'alert':1625 'alttext':774 'alway':437 'amount':766,977,988,1027,1107 'antigrav':3 'api':30,134,155,174,178,213,318,705,720,929,936,1117,1229,1250,1297,1374,1467,1638,1648,1716,1721,1900,1992,2085,2102,2106,2190,2431,2455,2480,2600,3047,3051,3060,3067,3071,3080,3165,3170,3177,3190,3197 'apikey':172,316,325,330,331,2731,2732 'apisecretkey':176 'apivers':211 'app':2,10,15,20,22,24,35,40,44,56,61,71,77,78,82,85,103,106,114,185,240,249,256,258,261,272,275,323,350,406,416,435,525,644,696,912,933,943,949,1201,1210,1223,1378,1382,1406,1436,1661,1914,1991,2028,2142,2179,2243,2406,2444,2449,2488,2499,2608,2623,2625,2632,2644,2654,2666,2671,2674,2692,2708,2727,2757,2767,2774,2777,2780,2804,2834,3311,3322,3332 'app._index.tsx':80 'app.hostorigin':2761 'app.tsx':84 'app.use':2357 'app/routes/app._index.tsx':348 'app/routes/app.billing.tsx':951 'app/routes/app.tsx':274,2719 'app/routes/webhooks.tsx':457,1485,2351,2853 'app/shopify.server.ts':150 'app/uninstalled':2200 'appdistribution.appstore':194 'appear':2634 'appli':252,1324,2234 'applic':121 'appprovid':290,328,2715,2721,2729 'apprecurringpr':1025 'apprecurringpricingdetail':1105 'approach':1627 'approv':1139,1193,1940,2016,2769 'appsubscript':1093 'appsubscriptioncr':1084 'appsubscriptionlineiteminput':1079 'appurl':183 'april':913,1931,2436,2484 'architect':3288 'ask':3373 'async':306,366,477,510,584,604,632,729,794,861,997,1040,1296,1489,1501,1539,1574,1759,1875,2051,2221,2857,2900,2942,2973,3122 'asynchron':581,1483 'attempt':1772,1774,1776,1806 'auth':89,91,144,2735,3266 'auth-specialist':3265 'authent':217,438,467,480,725,969,3242,3264 'authenticate.admin':312,374,441,737,802,1006,1049,3244 'authenticate.webhook':491,702,1497,1581,2865,3103 'authpathprefix':187 'auto':2702 'auto-upd':2701 'automat':2307,2828 'avail':931,2065,2077 'aw':3297 'await':311,373,380,388,490,512,519,531,535,553,595,615,736,745,786,801,806,818,841,871,1005,1013,1032,1048,1053,1072,1125,1303,1496,1503,1551,1580,1588,1612,1731,1736,1780,1800,1835,1880,2034,2079,2251,2529,2544,2568,2864,2873,2881,2889,2923,2937,2957,2963,2991,2995,2999,3003,3116 'awesom':4 'aws-serverless':3296 'backoff':1757 'badg':2688 'ban':1702 'banner':1335 'base':500,2206,3157 'bash':2237,2411 'basic':974,976,1168,1171 'becom':1662 'behavior':2137 'benefit':2685 'better':2686 'beyond':3268 'bill':33,935,940,1004,1147,1205 'block':2766 'blockstack':357,964,1268 'bodi':1308 'boolean':1083,1259 'boundari':3381 'break':515,523,539,557,657,664,676,1425,1555,1671,1929,2140,2305,2473,2649,2800,2877,2885,2893,3042 'bridg':25,259,417,436,934,2609,2645,2667,2675,2693,2709,3333 'build':269,1222,2441,2620 'built':420,2612,2628,2656,2682 'bulk':856,867,902,923,1703,1819,1831,2508 'bulkoper':891,1849 'bulkoperationrunmut':874,1838 'bulkupdateproduct':863 'bullmq':593,1531 'button':358,963,1164,1174,1270,1321 'call':877,1298,1468,1649,1722,1829,2557,3117,3191,3198 'callback':92 'callbackurl':2264 'cancel':1422,1463 'card':355,962 'cartlin':1347 'case':505,516,524,540,544,547,647,658,665,1548,2011,2869,2878,2886,3018,3022,3025 'catch':1810 'category-antigravity-awesome-skills' 'caus':1641,1692,2110,2269 'chang':254,2236 'charg':1069,1140,1199,1209 'chatbot':3291 'check':1008,1710,1726,2020,2245,2746,3044 'checkout':1215,1326 'checkoutbann':1345 'choos':1161 'chrome':2640 'clarif':3375 'clean':527 'clear':3348 'cli':63 'client':115,119,727 'code':246,2121,2157,2205,2523,3156 'code-bas':2204,3155 'collabor':3246 'common':2329 'communic':3277 'compar':1375 'complet':898 'complianc':640,2792 'compon':32,426 'condit':2174 'configur':98,107,242,253,2115,2379 'confirmationurl':1096 'conflict':2111,2177 'connect':409 'consid':3121,3180 'consol':2460 'console.log':493,559,1619,2073,3034 'console.warn':1632 'const':169,210,216,220,299,324,338,371,378,386,398,475,485,734,743,784,799,804,808,812,816,839,869,972,1002,1011,1030,1045,1051,1055,1063,1070,1123,1148,1151,1280,1285,1288,1294,1301,1346,1349,1487,1492,1532,1541,1572,1577,1609,1615,1729,1733,1738,1778,1792,1833,1867,2032,2056,2216,2249,2365,2369,2527,2542,2566,2739,2756,2759,2855,2860,2907,2921,2949 'continu':1458,1696,1807,2495 'correct':2635 'cost':1739,1741 'count':1705 'creat':58,507,626,866,1067,1550,2832 'createsubscript':1075 'criteria':3384 'critic':564,1511 'curl':2415,2424 'currencycod':767,979,990,1028,1109 'current':1009,1156,2246 'currentappinstal':1016,2037 'currentlyavail':1749 'cursor':775 'custom':200,204,541,545,648,652,659,662,1239,1243,1313,1314,1325,1886,1895,1907,1923,1935,1952,1957,1960,1971,1983,1997,2062,2074,2083,2091,2100,2813,2818,2870,2879,2911,2953,3019,3023,3263,3278 'customerdata':2922,2930,2941 'customeremail':2057,2061,2081,2969 'customerid':2908,2927,2950,2961,2966 'customernam':2971 'customers/data_request':2811 'customers/redact':2816 'customshopdomain':202 'cycl':1206 'dashboard':1990 'data':387,530,542,649,653,663,671,710,785,840,1031,1124,1281,1290,1440,1593,1734,1887,1908,1924,1936,1950,1972,1977,1984,1998,2005,2022,2030,2042,2063,2071,2075,2098,2793,2814,2819,2825,2845,2871,2912,2956,2968,2987,3020,3029,3040 'data.appsubscriptioncreate.confirmationurl':1143 'data.appsubscriptioncreate.usererrors':1133 'data.appsubscriptioncreate.usererrors.length':1128 'data.currentappinstallation.activesubscriptions':1037 'data.products':791 'data.productupdate.product':855 'data.productupdate.usererrors':849 'data.productupdate.usererrors.length':844 'data.shop':393 'data_request.destination':2919 'databas':1469,1565,1584 'dataset':860,1823 'date':625 'date.now':1611,1617 'day':656,984,995,1204,2019,2915 'db':471 'db.customer.deletemany':2958,2996 'db.customer.findunique':2924 'db.order.deletemany':3000 'db.order.updatemany':2964 'db.product.update':1589 'db.product.upsert':616 'db.session.deletemany':532,2992 'db.settings.deletemany':3004 'db.shopdata.delete':536 'declar':2151 'default':207,237,321,395,558,1145,1275,1341,1791,2350,2725 'defin':2144,2162,3150,3160 'definit':2109 'deleg':3247 'delet':661,668,2817,2822,2952,2985 'deliveri':1401,1903,2125,2285 'deliverymethod':158 'deploy':250,1379,1383,2232,2241,2244,3295,3302 'deprec':2457,3172 'describ':3352 'descript':3290 'design':430 'detail':1961 'detect':3179 'dev':2291 'develop':16,1189,1212,1917 'didn':1407 'differ':2103 'direct':928,2087 'distribut':193 'doesn':2318 'domain':201,205 'duplic':2107,2123,2171,3142 'durat':1616,1623,1630 'edg':758,771,1385,2257,2550,2583 'effect':2650 'either':2066 'email':385,1954,2059,2936 'email/sms':3273 'embed':21,255,262,270,2621,2670,2679,2734,2747,3321 'empti':1913 'enabl':235,1320,2733 'endcursor':778 'endpoint':2261,2277,2463,2493,3091 'enforc':1673 'entir':1464 'env':1121 'environ':3063,3075,3083,3364 'environment-specif':3363 'equival':2565 'error':339,848,1132,1643,1658,1695,1769,1811,1813,1815,1926,2787,3050,3070,3089,3128,3234 'errorboundari':337 'etc':594 'even':1962,2839,3007 'everi':982,993 'exceed':1690 'exist':2447,2498,2555 'expect':569,1427 'expert':11,3369 'explicit':1939 'exponenti':1756 'export':206,209,215,219,298,305,320,335,365,394,474,728,793,996,1039,1144,1274,1340,1486,1571,2724,2854 'express':2354 'extend':1213 'extens':36,102,104,1211,1224,1233,1236,1282,1300,1328,1364,1728,1735,1740 'extensions.capabilities':1249 'extensions.settings':1253 'extensions.settings.fields':1254 'extensions.targeting':1244,1329 'extensions/checkout-ext/src/checkout.tsx':1332 'extensions/my-extension':1228 'extensions/my-extension/src/adminblock.tsx':1263 'fail':684,1404,1448,1665,1904,1919,2132,2286,2293,2790 'failur':1459 'fast':1570 'featur':2467,2502,2513,3285 'fetch':1304,2530 'field':831,1098,1909,2050 'file':2398 'finish':904 'fire':2128 'first':749,754,755,769,780,2255,2548,2581 'fix':1479,1709,1980,2182,2339,2517,2705,2847 'fn':1762,1781,1878,1884 'format':2343,2410 'formdata':805,1052 'formdata.get':810,814,1057 'found':2283 'framework':2331 'free':1359 'full':1377 'function':307,322,336,367,396,585,605,633,730,795,862,998,1041,1146,1278,1344,1760,1876,2052,2726,2754,2901,2943,2974 'futur':195,2742 'gdpr':550,639,691,2764,2783,2808,2850,3035 'get':413,1785,2170,2560 'getproduct':748,2571 'go':1987 'grace':2043 'graphql':28,376,703,712,722,907,1684,1725,2435,2470,2491,2505,2515,2519,2540,2564,2593,2606,3174,3183 'graphql-on':2504 'handl':27,443,700,1240,1523,2039,2040,2230,2320,2340,2352,2797,2806,3187,3193,3202 'handlecustom':1295,1323 'handlecustomerredact':2882,2944 'handledatarequest':2874,2902 'handlegdprwebhook':554,634 'handleproductupd':520,606 'handler':95,1557,2394,2838,2851,3098,3110,3120,3136 'handleshopredact':2890,2975 'handlewebhook':1613 'happen':1472 'hardcod':3045,3056,3058,3065,3078 'hasnextpag':777 'header':1713,1789,2533 'heavi':689,1465 'high':1393,1645,1892,2439,2618,2771 'hmac':448,483,698,3086,3094,3100 'home':332 'hook':2155,2219 'hour':673,1456,2983 'href':303 'html':2713 'http':1653 'id':116,120,760,828,835,884,892,1018,1094,1293,1847,1850,2259,2552,2562,2572,2573,2575,2576,2577,2585,2589 'ifram':1368 'imag':768 'immedi':567,1481,1514,1693,2287 'imper':2158 'implement':938,1753,2786,2837,2848,3203 'impli':3309,3315,3320,3326,3331,3337 'import':151,153,161,165,279,289,293,352,361,458,466,470,952,960,968,1264,1333,1528,1857,2720,2750 'in-memori':3206,3213,3221 'includ':17,1951 'index':397 'individu':1828 'info':3168 'inform':1958 'init':65 'inlinestack':1271 'input':822,825,826,834,878,881,882,1841,1844,1845,3378 'instead':1825 'int':750 'integr':937,3255,3260 'interact':716 'interv':981,992,1029,1111,1873 'inventoryquant':2587 'isembed':2760 'isembeddedapp':329,2730 'issu':2641 'item':927 'job':591,686,1540 'job.data':1545 'jobqueue.add':596,1504 'json':315,391,789,847,853,953,1035,1131,2563 'json.stringify':1309 'juli':423,2615,2651 'justifi':2008 'key':175,319,1118,1255,3068,3072,3081 'keyof':1060 'lambda':3299 'larg':859,1822 'lasterror':1768,1812,1817 'late':231 'latest':67,154,212,2605,2663,2707 'layout':86,101,276,354 'legaci':2159,2482,2526 'let':1767,1771,2228 'level':2024 'limit':916,1373,1640,1676,1691,1707,1712,1723,1860,1868,3186,3201,3340 'limiter.removetokens':1881 'lineitem':1021,1078,1087,1088,1103 'link':280,300 'll':1444 'llm':3287 'llm-architect':3286 'loader':308,368,731,999,3240 'loaderfunctionarg':310,370,733,1001 'local':2290 'locat':2387,2395,2402 'log':1414,1606,2093,2296 'logic':3205 'long':1637 'lose':2681 'main':81,349 'maintain':2446 'make':1647 'mandatori':552,694 'mani':1656,2511 'manual':2788 'mark':1402 'match':427,3349 'math.pow':1804 'max':1202 'maximumavail':1747 'maxretri':1764,1775 'may':1461,2047,3137 'medium':2113,2272 'memori':3208,3215,3223 'mention':3307,3313,3318,3324,3329,3335 'merchant':1137,1191 'messag':832,895,1099,1853,3057,3077,3096,3114,3134,3154,3175,3196,3220,3239 'metafield':2507 'method':1169,1179,1306 'middlewar':2356 'migrat':2433,2554,3181 'minvariantpric':765 'miss':1415,2041,2466,2763,3085,3124,3184,3230,3386 'modern':42 'modul':1247 'monet':947 'monitor':1602 'ms':1624 'multipl':3115 'must':1192,1387,1471,2489,2660,2836,3014,3032,3053,3092,3130 'mutat':708,792,820,873,875,876,1074,1832,1837,1839,1840 'my-shopify-app':68,74,111 'mycompon':2755 'name':110,384,975,986,1019,1076,1085,1086,1101,1237,1260,1953 'navig':433,2638 'need':2004,3250,3262,3272,3283,3293 'never':3054 'new':54,59,190,239,575,624,910,1516,1534,1597,1869,2442,2486,2501,2512,2522,2895 'newembeddedauthstrategi':197,234,2744 'newtitl':813,838 'next':2360,2378 'nginx':2382,2383 'node':759,772,2258,2551,2584 'note':223,415,677,906,1184,1363 'noth':3011 'notif':3274,3279 'npm':64 'null':577,783,1362,1518,1599,1911,2897,2970,2972 'oauth':1947,3270 'octob':2476 'ok':1568 'old':2673 'onclick':1165,1175 'one':1195,1197 'onpress':1322 'oper':613,857,868,903,924,1470,1560,1664,1704,1820,2509 'order':132,506,599,1549,1956,1965 'order/product':1416 'orderdata':602 'orderid':600 'orders/create':140,2196 'orders/customers':1906 'orders/updated':2197 'outlet':281 'output':3358 'outsid':3280 'package.json':108 'page':83,351,353,961 'pageinfo':776 'pagin':742 'parseint':1794 'partial':1668 'partner':1989 'pass':2405 'path':887 'path-to-staged-upload':886 'pattern':12,37 'payload':488,514,522,556,587,603,609,637,1495,1510,1544,1554,1579,1614,2046,2054,2862,2876,2884,2892,2905,2947,2978 'payload.customer':2058 'payload.customer.id':2909,2951 'payload.data':2939 'payload.id':601,619,628,1592 'payload.title':622,631,1595 'payment':3251,3257 'per':919,1200,1680,1682,1687,1863 'perform':1604 'permiss':1890,3379 'person':2955 'pii':1896,1937 'place':2148,2168 'placement':2687 'plan':973,1022,1058,1062,1064,1065,1104,1157,1163,1167,1177 'plan.amount':1108 'plan.currencycode':1110 'plan.interval':1112 'plan.name':1102 'plankey':1056,1066 'pleas':345 'point':918,1686 'polari':31,425,2695,3327 'polarisstyl':294,304 'poll':896 'post':1170,1180,1307,2417,2426 'preview':1257,1262,1318,1319 'price':1026,1106,2586 'pricerangev2':764 'pricingdetail':1023 'prisma':166,192 'prismasessionstorag':162,191,3227 'pro':985,987,1178,1181 'process':446,499,511,580,598,690,1437,1466,1482,1502,1506,1522,1537,1621,1634,3106,3123,3252 'process-ord':597 'process-webhook':1505,1536 'process.env.node':1120 'process.env.scopes':181 'process.env.shop':199,203 'process.env.shopify':173,177,184,317,1116 'processord':1552,2053 'product':128,130,333,517,740,753,790,827,854,883,1122,1238,1242,1312,1315,1846,1921,2295,2547,2574,3289 'product-custom':1241 'productcustom':1279 'productid':809,811,836,1289,1310,1316,2591 'productinput':823,879,1842 'products/create':2198 'products/update':141,2199 'productupd':824,880,1843 'program':2631 'project':72 'promis':1763,1766,1879 'proper':3140 'protect':1885,1922,1934,1949,1976,1982,1996,2068 'provid':88,278,2812,2933 'proxi':2404 'public':911,2443,2487 'purchase.checkout.block.render':1331,1343 'qualifi':1357 'queri':377,382,706,723,739,747,1015,2026,2036,2253,2366,2377,2546,2570 'queue':508,592,687,1499,1854 'queueorderprocess':513,586 'quick':611,1562,1583,3113 'race':2173 'rate':915,1639,1675,1711,3185,3200 'ratelimit':1858,1870 'ratelimitedrequest':1877 'react':38,47,224,1372,2717 'reactextens':1265,1276,1334,1342 'read':127,131,1964 'receiv':453,494,1395,1970 'recommend':229,1478,1708,1979,2152,2181,2186,2338,2516,2541,2704,2846 'recur':1196,1208 'redact':546,549,660,667,2049,2072,2880,2888,3024,3027 'redirect':145,954,1136,1142 'regist':2134,2239 'registr':2176,2207,2225,3144,3159 'reject':2452,2626,2782 'rel':301 'remix':227,286,463,957 'remix-run':285,462,956 'remix/react':18,2345 'remov':2203,2223 'render':260 'repeat':1420 'replac':226 'req':2358 'req.path.endswith':2362 'req.path.length':2363,2368 'req.path.slice':2371 'req.url.slice':2367 'request':309,313,369,375,439,478,492,543,650,732,738,797,803,1000,1007,1043,1050,1490,1498,1575,1582,1657,1679,1855,1862,1981,2001,2298,2794,2858,2866,2872,2940,3021,3245 'request.formdata':807,1054 'requestedquerycost':1742 'requir':418,641,908,1888,1938,2432,2514,2610,2802,3377 'res':2359 'res.redirect':2374 'respond':583,678,1388,1409,1442,1480,3016,3112 'respons':379,570,576,744,817,870,1012,1071,1430,1475,1517,1598,1607,1660,1727,1730,1779,1809,1834,2033,2250,2528,2543,2567,2896,3126,3141,3195 'response.headers.get':1795 'response.json':389,787,842,1033,1126,1737 'response.status':1783 'rest':1677,1715,1866,2430,2454,2462,2478,2492,2525,2556,2559,3164,3169,3176 'restorer':1751 'result':1302,1699 'retri':1419,1447,1754,1787,1797,3204 'retry-aft':1786,1796 'retryaft':1793,1802 'return':314,327,341,390,401,565,574,651,788,846,852,1034,1130,1141,1154,1311,1355,1361,1512,1515,1596,1808,1883,2078,2280,2388,2728,2894,2910,3131,3139 'returnurl':1080,1089,1090,1113 'review':2789,3370 'rewrit':2392 'root':100 'root.tsx':99 'rout':79,3235 'router':19,39,48,225,2346 'run':247,287,464,958,1365 'safepath':2370,2376 'safeti':3380 'sandbox':1367 'scale':3219 'scope':125,126,180,1948,1966,3351 'script':2698,2711 'second':573,681,921,1391,1433,1587,1681,1689,1864,1874 'secret':179,3048,3052,3061 'secur':444 'seek':2655 'select':1291 'send':2917,2931 'senddatatomerch':2938 'sendorderconfirm':2080 'sent':1477 'separ':1945 'serv':2691 'server':97,2317,2381 'serverless':3294,3298 'session':1047,2222,3209,3216,3224,3231,3238 'session.shop':1114 'sessionstorag':189,221 'set':334,1286,2274 'settings.show':1317 'setup':41 'sever':1392,1644,1891,2112,2271,2438,2617,2770,3049,3069,3088,3107,3127,3145,3167,3188,3211,3233 'sharp':1384 'ship':1360 'shop':383,392,399,487,498,521,529,534,538,548,607,629,666,670,709,1494,1509,1543,1553,1720,2531,2824,2863,2875,2883,2887,2891,2903,2928,2945,2962,2967,2976,2980,2990,2994,2998,3002,3006,3026,3038 'shop.name':404 'shop/redact':2821 'shopifi':1,9,14,43,55,60,70,76,113,170,208,248,264,422,428,454,568,718,948,1214,1381,1398,1413,1426,1446,1651,1672,1719,1942,2015,2141,2217,2242,2306,2536,2614,2622,2630,2658,2684,2689,2740,2776,2801,3046,3059,3066,3079,3256,3269,3281,3310,3316,3338 'shopify.app.toml':105,109,244,2150,2188,2598,3163 'shopify.authenticate':218 'shopify.extension.toml':1226 'shopify.server.ts':96,2738 'shopify.sessionstorage':222 'shopify/app':66 'shopify/app-bridge-react':364,2753 'shopify/polaris':360,967 'shopify/polaris/build/esm/styles.css':296 'shopify/product':2590 'shopify/shopify-app-remix/adapters/node':152 'shopify/shopify-app-remix/react':292,2723 'shopify/shopify-app-remix/server':160 'shopify/shopify-app-session-storage-prisma':164 'shopify/ui-extensions-react':1370 'shopify/ui-extensions-react/admin':1273 'shopify/ui-extensions-react/checkout':1339 'shopifyapp':157,171,2218,2741 'shopifyid':618,627,1591,2926,2960 'shopifyrequest':1761 'show':1256,1261,2297 'sickn33':8 'signatur':484,3095 'silent':1666 'similar':3229 'simpl':1559,1564 'sinc':1930 'situat':1394,1646,1893,2114,2273,2440,2619,2772 'skill':5,6,3343 'slash':2268,2311,2337,2386,2414,2423 'sleep':1801 'someth':342 'source-sickn33' 'special':1889 'specialist':3267 'specif':3365 'split':182 'stage':889 'stageduploadpath':885,1848 'start':52,414,1610,1618,2483 'status':578,762,850,893,1020,1095,1134,1159,1519,1600,1851,2579,2659,2748,2898,3133 'still':3015 'stop':2464,3371 'storag':3210,3225 'store':412,697,1190,1683,2450,2768,2778,2844,3010,3030,3041 'storefront':1218 'strategi':2736 'strict':1674,2334 'string':608,636,752,1077,2904,2946,2977 'strip':2384 'stripe':3254,3259 'stripe-integr':3253 'structur':73 'stylesheet':302 'submiss':2451,2781 'submit':1152,1166,1176,2773 'subscrib':2829 'subscript':939,1010,1036,1068,1149,1155,1194,1959,2131,2172,2247 'subscription.name':1158 'subscription.status':1160 'substitut':3361 'success':3383 'switch':503,645,1546,2867 'symptom':1399,1652,1901,2122,2278,2448,2624,2779 'sync':612 'synchron':3104 'tag':2699,2712 'take':1635,2018 'target':1245,1330 'task':3347 'templat':45,57,230,273,456,721,950,1225 'temporari':1701 'test':1082,1091,1092,1119,1186,2408,2412,2421,3367 'text':356,965,1269 'three':2807 'throttl':1659 'throttlestatus':1746 'throw':1816 'time':1411,1453,1608 'timeout':1445,1628 'titl':621,630,761,815,829,837,1594,2553,2578 'titlebar':362,431 'token':2538,2539 'tokensperinterv':1871 'toml':2119,2184,2187,2215,2229,2235,2597,3152 'topic':139,486,495,502,504,555,562,563,635,646,1493,1508,1542,1547,1578,2195,2260,2861,2868,3036 'total':1350 'total.amount':1353 'totalinventori':763 'trail':2267,2310,2336,2385 'treat':3356 'tri':346,1777,2397 'trigger':3248 'true':198,1187,1252,2745 'tsx':90 'twice':2129 'twilio':3276 'twilio-commun':3275 'two':2147 'type':459,1234,1258,2006 'typeof':1061 'typescript':1484,1563,1605,1714,1758,1824,1856,2025,2044,2089,2208,2248,2344,2524,2558,2718,2737,2749,2852,3012 'ui':1235,1327 'unhandl':560 'uninstal':526,675,2981 'unpredict':2135 'unrespons':1663 'unstabl':196,233,2743 'unvers':2697 'updat':518,620,1417,1566,1585,2180,2703 'updatedat':623 'updateproduct':821 'upload':890 'uri':142,2201,2399,2400 'url':122,146,186,297,773,1081,2266,2314,2342,2920,2934 'usag':1198,2456,3166,3178 'usd':980,991 'use':51,268,452,589,685,715,900,922,946,1185,1221,1369,1818,1830,2010,2082,2183,2214,2490,2518,2592,2604,2661,2672,2706,2714,3062,3074,3082,3102,3173,3226,3243,3305,3341 'useapi':1266,1283 'useappbridg':2751,2758 'usecartlin':1336,1348 'useloaderdata':282,326,400,1150 'user':3249,3261,3271,3282,3292,3306,3312,3317,3323,3328,3334 'usererror':830,894,1097,1852 'userouteerror':283,340 'useset':1267,1287 'usesubmit':1153 'usetotalamount':1337,1351 'usual':2916 'valid':3043,3232,3237,3366 'variabl':779,833,1100,2588,3064,3076,3084 'variant':2580 'vercel':3301 'verif':449,699,3087,3101 'verifi':482,3093 'version':135,156,214,1230,2191,2601,2607,2646,2664,2676 'via':2696,2710 'violat':1697 'wait':2013 'warn':2458,2642,3108,3146,3189,3212 'web':2380 'webhook':26,94,133,241,442,445,455,481,496,551,561,683,692,901,905,1386,1396,1400,1418,1439,1449,1460,1507,1538,1603,1620,1633,1898,1902,1974,2045,2108,2116,2124,2127,2130,2136,2145,2165,2189,2224,2238,2265,2276,2279,2284,2313,2326,2401,2403,2595,2599,2765,2784,2795,2809,3090,3097,3105,3109,3119,3125,3129,3135,3143,3147,3158,3161,3339 'webhookhttpendpoint':2263 'webhooks.subscriptions':138,2194 'webhooks.tsx':93 'webhooksubscript':2254 'welcom':402 'went':343 'window.location.origin':2762 'within':571,654,679,1389,1431,2913 'without':1975,2413,3099,3199,3241 'won':1968 'work':1526,1915,2288,2348,2465,2496 'worker':1521,1529,1533,1535 'workers/webhook-processor.ts':1527 'write':129 'wrong':344 'x':1718,2416,2425,2535 'x-shopify-access-token':2534 'x-shopify-shop-api-call-limit':1717 'your-app.com':2419,2428 'your-app.com/webhooks':2418 'your-app.com/webhooks/':2427 'your-app.example.com':123,148 'your-app.example.com/auth/callback':147 'your-client-id':117","prices":[{"id":"10fd93c1-29af-43f0-9f33-aa5e73e9f82a","listingId":"77f495fd-4b99-47bf-8acf-ad45317ef974","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T20:37:51.333Z"}],"sources":[{"listingId":"77f495fd-4b99-47bf-8acf-ad45317ef974","source":"github","sourceId":"sickn33/antigravity-awesome-skills/shopify-apps","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/shopify-apps","isPrimary":false,"firstSeenAt":"2026-04-18T21:44:50.646Z","lastSeenAt":"2026-04-25T06:51:59.794Z"},{"listingId":"77f495fd-4b99-47bf-8acf-ad45317ef974","source":"skills_sh","sourceId":"sickn33/antigravity-awesome-skills/shopify-apps","sourceUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/shopify-apps","isPrimary":true,"firstSeenAt":"2026-04-18T20:37:51.333Z","lastSeenAt":"2026-04-25T09:40:47.538Z"}],"details":{"listingId":"77f495fd-4b99-47bf-8acf-ad45317ef974","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"shopify-apps","source":"skills_sh","category":"antigravity-awesome-skills","skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/shopify-apps"},"updatedAt":"2026-04-25T09:40:47.538Z"}}