home / skills / trantuananh-17 / product-reviews / shopify-api

shopify-api skill

/.claude/skills/shopify-api

This skill helps you integrate Shopify APIs securely and efficiently by applying version checks, rate limiting, and best practices across GraphQL, webhooks,

npx playbooks add skill trantuananh-17/product-reviews --skill shopify-api

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
5.9 KB
---
name: shopify-api-integration
description: Use this skill when the user asks about "Shopify GraphQL", "Admin API", "metafields", "webhooks", "rate limiting", "pagination", "App Bridge", or any Shopify API integration work. Provides Shopify API patterns, rate limit handling, and best practices.
---

# Shopify API Best Practices

## API Version Check (CRITICAL)

**Always verify API version before implementing!**

Shopify deprecates API versions regularly. Check:
1. Current API version in `shopify.app.toml` or app config
2. Shopify release notes for breaking changes
3. Use Shopify MCP tools to verify current schema

```javascript
// Check what version your app uses
// shopify.app.toml
[api]
api_version = "2024-10"  // Verify this matches your implementation
```

---

## API Selection Guide

| Need | Solution |
|------|----------|
| Customize checkout UI | Checkout UI Extension |
| Apply discounts | Discount Function |
| Validate cart | Cart Validation Function |
| React to events | Webhooks |
| Read/write data | GraphQL Admin API |
| Sync large data | Bulk Operations |
| Store custom data | Metafields/Metaobjects |

---

## GraphQL Admin API

### Basic Query

```javascript
const query = `
  query getProduct($id: ID!) {
    product(id: $id) {
      id
      title
      handle
      variants(first: 10) {
        nodes {
          id
          price
        }
      }
    }
  }
`;

const response = await shopify.graphql(query, { id: productId });
```

### Pagination

```javascript
async function getAllProducts(shopify) {
  const products = [];
  let hasNextPage = true;
  let cursor = null;

  while (hasNextPage) {
    const query = `
      query getProducts($cursor: String) {
        products(first: 50, after: $cursor) {
          pageInfo { hasNextPage }
          edges {
            cursor
            node { id title }
          }
        }
      }
    `;

    const response = await shopify.graphql(query, { cursor });
    const { edges, pageInfo } = response.products;

    products.push(...edges.map(e => e.node));
    hasNextPage = pageInfo.hasNextPage;
    cursor = edges[edges.length - 1]?.cursor;
  }

  return products;
}
```

---

## Bulk Operations (ALWAYS Consider First)

**Before implementing any Shopify data sync, ask: "Can this hit API limits?"**

**Rate Limits Context:**
- Regular metafield API: **2 requests/second**, **40 requests/minute**
- Bulk Operations: **No rate limits** - runs server-side on Shopify

### Volume Decision Guide

| Volume | Strategy |
|--------|----------|
| < 50 items | Regular GraphQL |
| 50-500 items | Batch with Cloud Tasks + rate limiting |
| **500+ items** | **Bulk Operations API** |

**For detailed bulk mutation patterns, see:** `shopify-bulk-operations` skill

---

## Rate Limiting

### Cloud Tasks (Recommended for Rate Limits)

```javascript
// BAD: In-function sleep wastes CPU time
await sleep(60000); // 60s sleep = 60s CPU billed

// GOOD: Schedule retry with Cloud Tasks
async function scheduleRetry(payload, delaySeconds) {
  await client.createTask({
    parent: client.queuePath(project, location, 'shopify-retry'),
    task: {
      httpRequest: {
        url: `${baseUrl}/api/retry-shopify`,
        body: Buffer.from(JSON.stringify(payload)).toString('base64'),
        headers: { 'Content-Type': 'application/json' }
      },
      scheduleTime: {
        seconds: Math.floor(Date.now() / 1000) + delaySeconds
      }
    }
  });
}
```

---

## Metafields

### Set Metafields (Batch)

```javascript
const mutation = `
  mutation metafieldsSet($metafields: [MetafieldsSetInput!]!) {
    metafieldsSet(metafields: $metafields) {
      metafields { id key value }
      userErrors { field message }
    }
  }
`;

await shopify.graphql(mutation, {
  metafields: [
    {
      ownerId: customerId,
      namespace: 'loyalty',
      key: 'points',
      type: 'number_integer',
      value: '500'
    },
    {
      ownerId: customerId,
      namespace: 'loyalty',
      key: 'tier',
      type: 'single_line_text_field',
      value: 'Gold'
    }
  ]
});
```

---

## Webhooks

### Response Time (CRITICAL)

**Must respond within 5 seconds!**

```javascript
// BAD: Heavy processing (may timeout)
app.post('/webhooks/orders/create', async (req, res) => {
  await calculatePoints(req.body);
  await updateCustomer(req.body);
  await syncToShopify(req.body);
  res.status(200).send('OK');
});

// GOOD: Queue and respond fast
app.post('/webhooks/orders/create', async (req, res) => {
  // Quick validation
  if (!verifyHmac(req)) {
    return res.status(401).send('Unauthorized');
  }

  // Queue for background processing
  await webhookQueueRef.add({
    type: 'orders/create',
    payload: req.body
  });

  // Respond immediately
  res.status(200).send('OK');
});
```

### HMAC Verification

```javascript
import crypto from 'crypto';

function verifyHmac(req) {
  const hmac = req.get('X-Shopify-Hmac-Sha256');
  const body = req.rawBody;
  const secret = process.env.SHOPIFY_WEBHOOK_SECRET;

  const hash = crypto
    .createHmac('sha256', secret)
    .update(body, 'utf8')
    .digest('base64');

  return crypto.timingSafeEqual(
    Buffer.from(hmac),
    Buffer.from(hash)
  );
}
```

---

## App Bridge (Direct API)

### When to Use

| Scenario | Use App Bridge | Use Firebase API |
|----------|---------------|------------------|
| Simple Shopify CRUD | Yes | No |
| Need Firestore data | No | Yes |
| Complex business logic | No | Yes |
| Background processing | No | Yes |

### Direct API Call

```javascript
import { authenticatedFetch } from '@shopify/app-bridge/utilities';

async function fetchProducts(app) {
  const response = await authenticatedFetch(app)(
    '/admin/api/2024-04/graphql.json',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        query: `{ products(first: 10) { nodes { id title } } }`
      })
    }
  );

  return response.json();
}
```

**Benefits:**
- Faster (no Firebase roundtrip)
- Lower cost (no function invocation)
- Uses shop's session directly

Overview

This skill helps developers integrate with Shopify's APIs, focusing on GraphQL Admin API patterns, metafields, webhooks, pagination, rate limiting, Bulk Operations, and App Bridge usage. It condenses practical guidance for choosing the right API surface, protecting webhook endpoints, and scaling data syncs while avoiding common pitfalls. Use it to implement robust, production-ready Shopify app integrations.

How this skill works

The skill inspects the integration needs (read/write volume, real-time vs background work, session context) and recommends the appropriate Shopify surface: GraphQL Admin API, Bulk Operations, Checkout Extensions, or App Bridge. It outlines pagination and cursor patterns, batch metafield set mutations, webhook response and HMAC verification best practices, and rate-limit mitigation using background task queues. It also explains when to prefer direct App Bridge calls versus server-side APIs.

When to use it

  • Building read/write features for products, customers, orders using GraphQL Admin API
  • Syncing large datasets (500+ items) where Bulk Operations are preferable
  • Processing webhooks without blocking by queuing background work
  • Setting or updating many metafields in batches
  • Implementing authenticated UI flows inside the Shopify admin using App Bridge

Best practices

  • Always verify and lock to the correct API version before implementation and monitor Shopify release notes
  • Prefer Bulk Operations for large-volume syncs to avoid rate limits; use regular GraphQL for small batches
  • Respond to webhooks within 5 seconds: validate HMAC then enqueue heavy work
  • Avoid in-function sleeps for rate limiting; schedule retries with a task queue (e.g., Cloud Tasks)
  • Use cursor-based pagination to iterate collections and stop when pageInfo.hasNextPage is false
  • Use App Bridge for direct admin-page calls for speed and lower cost; use server-side APIs for complex logic or Firestore access

Example use cases

  • Fetch all products via cursor pagination and transform to an internal catalog
  • Perform a bulk mutation to migrate 10,000 product metafields without hitting per-second limits
  • Receive orders/create webhooks, verify HMAC, enqueue for loyalty points processing, and respond 200 immediately
  • Use App Bridge authenticatedFetch to render admin-embedded product lists without extra server hops
  • Throttle high-volume metafield updates by queuing batched GraphQL mutations with scheduled retries

FAQ

When should I use Bulk Operations over regular GraphQL?

Choose Bulk Operations for large-scale data syncs (typically 500+ items). For smaller volumes, batch regular GraphQL calls with rate limiting.

How do I avoid webhook timeouts?

Verify HMAC quickly, enqueue payloads for background processing, and return a 200 within 5 seconds to Shopify.