shopify-known-pitfalls
Identify and avoid Shopify API anti-patterns: ignoring userErrors, wrong API version, REST instead of GraphQL, missing GDPR webhooks, and webhook timeout issues. Trigger with phrases like "shopify mistakes", "shopify anti-patterns", "shopify pitfalls", "shopify what not to do", "shopify code review".
Best use case
shopify-known-pitfalls is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Identify and avoid Shopify API anti-patterns: ignoring userErrors, wrong API version, REST instead of GraphQL, missing GDPR webhooks, and webhook timeout issues. Trigger with phrases like "shopify mistakes", "shopify anti-patterns", "shopify pitfalls", "shopify what not to do", "shopify code review".
Teams using shopify-known-pitfalls should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/shopify-known-pitfalls/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How shopify-known-pitfalls Compares
| Feature / Agent | shopify-known-pitfalls | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Identify and avoid Shopify API anti-patterns: ignoring userErrors, wrong API version, REST instead of GraphQL, missing GDPR webhooks, and webhook timeout issues. Trigger with phrases like "shopify mistakes", "shopify anti-patterns", "shopify pitfalls", "shopify what not to do", "shopify code review".
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
Related Guides
Best AI Skills for Claude
Explore the best AI skills for Claude and Claude Code across coding, research, workflow automation, documentation, and agent operations.
AI Agents for Coding
Browse AI agent skills for coding, debugging, testing, refactoring, code review, and developer workflows across Claude, Cursor, and Codex.
ChatGPT vs Claude for Agent Skills
Compare ChatGPT and Claude for AI agent skills across coding, writing, research, and reusable workflow execution.
SKILL.md Source
# Shopify Known Pitfalls
## Overview
The 10 most common mistakes when building Shopify apps, with real API examples showing the wrong way and the right way.
## Prerequisites
- Shopify app codebase to review
- Understanding of GraphQL Admin API patterns
## Instructions
### Pitfall #1: Not Checking userErrors (The #1 Mistake)
Shopify GraphQL mutations return HTTP 200 even when they fail. The errors are in `userErrors`.
```typescript
// WRONG — assumes 200 means success
const response = await client.request(PRODUCT_CREATE, { variables });
const product = response.data.productCreate.product; // null!
console.log(product.title); // TypeError: Cannot read property 'title' of null
// RIGHT — always check userErrors
const response = await client.request(PRODUCT_CREATE, { variables });
const { product, userErrors } = response.data.productCreate;
if (userErrors.length > 0) {
console.error("Shopify validation failed:", userErrors);
// [{ field: ["title"], message: "Title can't be blank", code: "BLANK" }]
throw new ShopifyValidationError(userErrors);
}
console.log(product.title); // Safe
```
---
### Pitfall #2: Using REST When GraphQL Is Required
REST Admin API is legacy as of October 2024. New public apps after April 2025 **must** use GraphQL.
```typescript
// WRONG — REST API (legacy, higher bandwidth, returns all fields)
const { body } = await restClient.get({ path: "products", query: { limit: 250 } });
// Returns EVERYTHING: body_html, template_suffix, published_scope...
// RIGHT — GraphQL (get only what you need)
const response = await graphqlClient.request(`{
products(first: 50) {
edges { node { id title status } }
pageInfo { hasNextPage endCursor }
}
}`);
```
---
### Pitfall #3: Ignoring API Version Deprecation
Shopify deprecates API versions ~12 months after release. Your app will break silently when your version is removed.
```typescript
// WRONG — hardcoded old version, no monitoring
const shopify = shopifyApi({ apiVersion: "2023-04" }); // DEAD version
// RIGHT — use recent stable version, monitor deprecation
const shopify = shopifyApi({ apiVersion: "2024-10" });
// Monitor for deprecation warnings in responses
function checkDeprecation(headers: Headers): void {
const warning = headers.get("x-shopify-api-deprecated-reason");
if (warning) {
console.warn(`[DEPRECATION] ${warning}`);
// Alert team to upgrade
}
}
```
---
### Pitfall #4: Missing Mandatory GDPR Webhooks
Your app **will be rejected** from the App Store without these three webhooks.
```typescript
// WRONG — no GDPR handlers
// shopify.app.toml has no webhook subscriptions
// App Store review: REJECTED
// RIGHT — all three mandatory webhooks
// shopify.app.toml:
// [[webhooks.subscriptions]]
// topics = ["customers/data_request"]
// uri = "/webhooks/gdpr/data-request"
//
// [[webhooks.subscriptions]]
// topics = ["customers/redact"]
// uri = "/webhooks/gdpr/customers-redact"
//
// [[webhooks.subscriptions]]
// topics = ["shop/redact"]
// uri = "/webhooks/gdpr/shop-redact"
```
---
### Pitfall #5: Webhook Handler Takes Too Long
Shopify expects a 200 response within 5 seconds. If your handler does API calls inline, it will time out and Shopify will retry — causing duplicates.
```typescript
// WRONG — processing inline, takes 10+ seconds
app.post("/webhooks", rawBodyParser, async (req, res) => {
const order = JSON.parse(req.body);
await syncToERP(order); // 3 seconds
await updateInventory(order); // 2 seconds
await sendNotification(order); // 2 seconds
res.status(200).send("OK"); // 7+ seconds — Shopify considers this failed!
});
// RIGHT — respond immediately, process async
app.post("/webhooks", rawBodyParser, async (req, res) => {
res.status(200).send("OK"); // Respond within milliseconds
// Process asynchronously
const order = JSON.parse(req.body);
await queue.add("process-order", order);
});
```
---
### Pitfall #6: Using ProductInput on API 2024-10+
The `ProductInput` type was split into `ProductCreateInput` and `ProductUpdateInput` in 2024-10.
```typescript
// WRONG — old ProductInput type (breaks on 2024-10+)
mutation($input: ProductInput!) { // ERROR: ProductInput is not defined
productCreate(input: $input) { ... }
}
// RIGHT — separate types for create and update
mutation($input: ProductCreateInput!) {
productCreate(product: $input) { ... } // Note: "product:" not "input:"
}
mutation($input: ProductUpdateInput!) {
productUpdate(product: $input) { ... }
}
```
---
### Pitfall #7: Not Using Cursor Pagination
Shopify uses Relay-style cursor pagination, not page numbers.
```typescript
// WRONG — trying page numbers (doesn't work in GraphQL)
const page1 = await query("products(first: 50, page: 1)"); // ERROR
const page2 = await query("products(first: 50, page: 2)"); // ERROR
// RIGHT — cursor-based pagination
let cursor = null;
let hasMore = true;
while (hasMore) {
const response = await client.request(`{
products(first: 50, after: ${cursor ? `"${cursor}"` : "null"}) {
edges { node { id title } cursor }
pageInfo { hasNextPage endCursor }
}
}`);
// Process products...
cursor = response.data.products.pageInfo.endCursor;
hasMore = response.data.products.pageInfo.hasNextPage;
}
```
---
### Pitfall #8: Requesting 250 Items Per Page
`first: 250` with nested connections creates enormous query costs that THROTTLE immediately.
```typescript
// WRONG — cost explosion
// products(first: 250) × variants(first: 100) = 25,000 point cost
const response = await client.request(`{
products(first: 250) {
edges { node {
variants(first: 100) { edges { node { id price } } }
}}
}
}`);
// Result: THROTTLED immediately
// RIGHT — reasonable page sizes
const response = await client.request(`{
products(first: 50) {
edges { node {
variants(first: 10) { edges { node { id price } } }
}}
pageInfo { hasNextPage endCursor }
}
}`);
```
---
### Pitfall #9: Exposing Admin Token in Client-Side Code
Admin API tokens have full access. Never send them to the browser.
```typescript
// WRONG — admin token in React component
const response = await fetch(`https://store.myshopify.com/admin/api/2024-10/graphql.json`, {
headers: { "X-Shopify-Access-Token": "shpat_xxx" }, // Visible in browser devtools!
});
// RIGHT — proxy through your server
// Client calls your API, your server calls Shopify
const response = await fetch("/api/shopify/products"); // Your server
// Server-side only
app.get("/api/shopify/products", async (req, res) => {
const { admin } = await authenticate.admin(req);
const data = await admin.graphql(PRODUCTS_QUERY);
res.json(data);
});
```
---
### Pitfall #10: Not Handling APP_UNINSTALLED Webhook
When a merchant uninstalls your app, you need to clean up sessions. Otherwise, stale sessions cause auth loops.
```typescript
// WRONG — no cleanup on uninstall
// Result: when merchant reinstalls, old stale session is found,
// API calls fail with 401, auth redirect loop
// RIGHT — clean up on uninstall
async function handleAppUninstalled(shop: string): Promise<void> {
// Delete session from database
await prisma.session.deleteMany({ where: { shop } });
// Disable features for this shop
await prisma.appSettings.update({
where: { shop },
data: { active: false },
});
console.log(`Cleaned up data for uninstalled shop: ${shop}`);
// shop/redact webhook will fire 48 hours later for full data deletion
}
```
## Output
- Anti-patterns identified in codebase
- Fixes prioritized (security first, then correctness)
- Prevention measures in place (linting, CI checks)
## Error Handling
| Pitfall | How to Detect | Prevention |
|---------|--------------|------------|
| Missing userErrors check | Null pointer crashes | ESLint rule or wrapper function |
| REST usage | `grep -r "clients.Rest" src/` | Migration guide + lint rule |
| Old API version | `grep -r "apiVersion" src/` | CI check against supported versions |
| Missing GDPR webhooks | App Store rejection | Pre-submit compliance checker |
| Webhook timeout | Shopify retry storms | Queue-based processing |
| ProductInput on 2024-10 | GraphQL type error | Update mutations |
| Page-based pagination | Query errors | Use cursor pagination pattern |
| `first: 250` | THROTTLED responses | Query cost budgets |
| Admin token in client | Security audit | Server-side proxy |
| No APP_UNINSTALLED | Auth loops on reinstall | Webhook handler + session cleanup |
## Examples
### Quick Pitfall Scan
```bash
# Run these against your Shopify codebase
echo "=== Shopify Pitfall Scan ==="
echo -n "REST API usage: "; grep -rc "clients.Rest\|admin-rest" app/ src/ 2>/dev/null | grep -v ":0" | wc -l
echo -n "Missing userErrors check: "; grep -rn "mutation\|Mutation" app/ src/ --include="*.ts" | wc -l
echo -n "Old API versions: "; grep -rn "2023-\|2022-" app/ src/ --include="*.ts" 2>/dev/null | wc -l
echo -n "Hardcoded tokens: "; grep -rc "shpat_" app/ src/ 2>/dev/null | grep -v ":0" | wc -l
echo -n "first: 250: "; grep -rn "first: 250\|first:250" app/ src/ --include="*.ts" 2>/dev/null | wc -l
```
## Resources
- [Shopify App Requirements](https://shopify.dev/docs/apps/launch/app-requirements)
- [GraphQL Migration Guide](https://shopify.dev/docs/apps/build/graphql/migrate/learn-how)
- [2024-10 Breaking Changes](https://shopify.dev/docs/api/release-notes/2024-10)
- [Webhook Best Practices](https://shopify.dev/docs/apps/build/webhooks)
## Quick Reference Card
| Pitfall | Detection | Fix |
|---------|-----------|-----|
| No userErrors check | Null crashes on mutations | Always check `userErrors.length > 0` |
| REST instead of GraphQL | `grep "clients.Rest"` | Migrate to `clients.Graphql` |
| Old API version | `grep "2023-"` | Update to `2024-10` |
| Missing GDPR webhooks | App Store rejection | Add 3 mandatory webhook handlers |
| Webhook timeout | Retry storms, duplicates | Respond 200 immediately, queue processing |
| ProductInput on 2024-10 | Type error | Use `ProductCreateInput` / `ProductUpdateInput` |
| Page-number pagination | Query errors | Use cursor-based with `pageInfo` |
| `first: 250` with nesting | THROTTLED | Use `first: 50` or smaller |
| Admin token in browser | Security scan | Server-side proxy only |
| No APP_UNINSTALLED | Auth loop on reinstall | Clean up sessions on uninstall |Related Skills
windsurf-known-pitfalls
Identify and avoid Windsurf anti-patterns and common mistakes. Use when onboarding new developers to Windsurf, reviewing AI workflow practices, or auditing Windsurf configuration for issues. Trigger with phrases like "windsurf mistakes", "windsurf anti-patterns", "windsurf pitfalls", "windsurf what not to do", "windsurf gotchas".
vercel-known-pitfalls
Identify and avoid Vercel anti-patterns and common integration mistakes. Use when reviewing Vercel code for issues, onboarding new developers, or auditing existing Vercel deployments for best practice violations. Trigger with phrases like "vercel mistakes", "vercel anti-patterns", "vercel pitfalls", "vercel what not to do", "vercel code review".
supabase-known-pitfalls
Avoid and fix the most common Supabase mistakes: exposing service_role key in client bundles, forgetting to enable RLS, not using connection pooling in serverless, .single() throwing on empty results, missing .select() after insert/update, not destructuring { data, error }, creating multiple client instances, and not using generated types. Use when reviewing Supabase code, onboarding developers, auditing an existing project, or debugging unexpected behavior. Trigger with phrases like "supabase mistakes", "supabase anti-patterns", "supabase pitfalls", "supabase code review", "supabase gotchas", "supabase debugging", "what not to do supabase", "supabase common errors".
snowflake-known-pitfalls
Identify and avoid Snowflake anti-patterns and common mistakes in SQL, warehouse management, data loading, and access control. Use when reviewing Snowflake configurations, onboarding new users, or auditing existing Snowflake deployments for best practices. Trigger with phrases like "snowflake mistakes", "snowflake anti-patterns", "snowflake pitfalls", "snowflake what not to do", "snowflake code review".
shopify-upgrade-migration
Upgrade Shopify API versions and migrate from REST to GraphQL with breaking change detection. Use when upgrading API versions, migrating from deprecated REST endpoints, or handling Shopify's quarterly API release cycle. Trigger with phrases like "upgrade shopify", "shopify API version", "shopify breaking changes", "migrate REST to GraphQL", "shopify deprecation".
shopify-security-basics
Apply Shopify security best practices for API credentials, webhook HMAC validation, and access scope management. Use when securing API keys, validating webhook signatures, or auditing Shopify security configuration. Trigger with phrases like "shopify security", "shopify secrets", "secure shopify", "shopify HMAC", "shopify webhook verify".
shopify-sdk-patterns
Apply production-ready patterns for @shopify/shopify-api including typed GraphQL clients, session management, and retry logic. Use when implementing Shopify integrations, refactoring SDK usage, or establishing team coding standards for Shopify. Trigger with phrases like "shopify SDK patterns", "shopify best practices", "shopify code patterns", "idiomatic shopify", "shopify client wrapper".
shopify-reliability-patterns
Implement reliability patterns for Shopify apps including circuit breakers for API outages, webhook retry handling, and graceful degradation. Trigger with phrases like "shopify reliability", "shopify circuit breaker", "shopify resilience", "shopify fallback", "shopify retry webhook".
shopify-reference-architecture
Implement Shopify app reference architecture with Remix, Prisma session storage, and the official app template patterns. Trigger with phrases like "shopify architecture", "shopify app structure", "shopify project layout", "shopify Remix template", "shopify app design".
shopify-rate-limits
Handle Shopify API rate limits for both REST (leaky bucket) and GraphQL (calculated query cost). Use when hitting 429 errors, implementing retry logic, or optimizing API request throughput. Trigger with phrases like "shopify rate limit", "shopify throttling", "shopify 429", "shopify THROTTLED", "shopify query cost", "shopify backoff".
shopify-prod-checklist
Execute Shopify app production deployment checklist covering App Store requirements, mandatory webhooks, API versioning, and rollback procedures. Trigger with phrases like "shopify production", "deploy shopify", "shopify go-live", "shopify launch checklist", "shopify app store submit".
shopify-policy-guardrails
Implement Shopify app policy enforcement with ESLint rules for API key detection, query cost budgets, and App Store compliance checks. Trigger with phrases like "shopify policy", "shopify lint", "shopify guardrails", "shopify compliance", "shopify eslint", "shopify app review".