mcpField
S
shopify-admin-api

shopify-admin-api

github.com/finsilabs開発6087 回インストール5 日前に更新
リポジトリ

shopify-admin-api について

Shopifyアプリやストア向け社内ツールを開発する人のためのスキルです。Admin APIによるデータ操作を、GraphQL、REST、バルク処理、在庫更新、エラー対応とともに説明します。

主な機能

  1. 01GraphQLでのデータ取得と変更
  2. 02簡単な検索でのREST利用
  3. 03非同期バルクエクスポート
  4. 04注文と在庫の更新
  5. 05権限・レート制限・エラーへの対応

活用シーン

  1. 01Shopifyの顧客情報とCRMの同期
  2. 02外部システムからのフルフィルメント更新
  3. 03商品や注文の一括エクスポート
  4. 04倉庫データに基づく在庫調整

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill shopify-admin-api

インストール後、Claude Code で/shopify-admin-apiを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

tile.json
evals/bulk-product-export-with-gid-ids/
criteria.json
task.md
evals/customer-tag-management-and-rate-limit-h/
criteria.json
task.md
evals/order-fulfillment-and-usererrors-handlin/
criteria.json
task.md

詳細

Shopify Admin API

概要

Shopify Admin API を使うと、アプリからマーチャントのストアデータ(商品、バリエーション、注文、顧客、在庫、メタフィールドなど)に完全にアクセスできます。GraphQL(推奨)と REST の両方を利用でき、GraphQL では必要なフィールドを正確に選択できるほか、一括操作や計算コストシステムによる優れたレート制限を利用できます。@shopify/shopify-api Node.js ライブラリを使うか、Admin API アクセストークンを使って HTTP リクエストを直接送信してください。

このスキルを使用する場面

  • 商品カタログデータ(タイトル、バリエーション、価格、画像、在庫)の読み取りや書き込みを行う場合
  • 外部システムからプログラムで注文のフルフィルメントや更新を行う場合
  • Shopify と CRM または ERP の間で顧客レコードを同期する場合
  • Bulk Operations を使ってデータを一括エクスポートまたはインポートする場合
  • Custom App トークンを使ってマーチャントのストアにアクセスする内部ツールを構築する場合
  • 倉庫管理システムから在庫調整を自動化する場合

基本手順

  1. Admin API アクセストークンを取得する

    カスタムアプリ(単一ストア)の場合は、Admin → Settings → Apps and Sales Channels → Develop apps で作成します。公開アプリ/パートナーアプリの場合、OAuth 後にトークンを取得します(@shopify-app-development を参照)。

    # .env
    SHOPIFY_ADMIN_API_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    SHOPIFY_SHOP=mystore.myshopify.com
    SHOPIFY_API_VERSION=2025-01
    
  2. Admin API クライアントを初期化する

    // lib/shopify-admin.ts
    import { shopifyApi, ApiVersion, Session } from "@shopify/shopify-api";
    import "@shopify/shopify-api/adapters/node";
    
    const shopify = shopifyApi({
      apiKey: process.env.SHOPIFY_API_KEY!,
      apiSecretKey: process.env.SHOPIFY_API_SECRET!,
      scopes: ["read_products", "write_products", "read_orders", "write_orders"],
      hostName: process.env.SHOPIFY_APP_URL!,
      apiVersion: ApiVersion.January25,
      isEmbeddedApp: false, // true for merchant-facing embedded apps
    });
    
    // For custom apps with a static token
    const session = new Session({
      id: `offline_${process.env.SHOPIFY_SHOP}`,
      shop: process.env.SHOPIFY_SHOP!,
      state: "",
      isOnline: false,
      accessToken: process.env.SHOPIFY_ADMIN_API_TOKEN,
    });
    
    export const adminClient = new shopify.clients.Graphql({ session });
    export const restClient = new shopify.clients.Rest({ session });
    
  3. GraphQL で商品をクエリする

    // Fetch products with variants and inventory
    export async function getProducts(cursor?: string) {
      const response = await adminClient.request(`
        query GetProducts($cursor: String) {
          products(first: 50, after: $cursor) {
            pageInfo { hasNextPage endCursor }
            edges {
              node {
                id
                title
                status
                variants(first: 100) {
                  edges {
                    node {
                      id
                      sku
                      price
                      inventoryQuantity
                      inventoryItem { id }
                    }
                  }
                }
              }
            }
          }
        }
      `, { variables: { cursor } });
      return response.data.products;
    }
    
    // Update a product's price via mutation
    export async function updateVariantPrice(variantId: string, price: string) {
      const response = await adminClient.request(`
        mutation UpdateVariantPrice($id: ID!, $price: Money!) {
          productVariantUpdate(input: { id: $id, price: $price }) {
            productVariant { id price }
            userErrors { field message }
          }
        }
      `, { variables: { id: variantId, price } });
    
      const { userErrors } = response.data.productVariantUpdate;
      if (userErrors.length > 0) throw new Error(userErrors[0].message);
      return response.data.productVariantUpdate.productVariant;
    }
    
  4. 注文を取得、更新する

    // Query unfulfilled orders
    export async function getUnfulfilledOrders() {
      const response = await adminClient.request(`
        query {
          orders(first: 50, query: "fulfillment_status:unfulfilled financial_status:paid") {
            edges {
              node {
                id
                name
                email
                createdAt
                lineItems(first: 50) {
                  edges {
                    node {
                      title
                      quantity
                      variant { id sku }
                    }
                  }
                }
                shippingAddress {
                  firstName lastName address1 city province zip country
                }
              }
            }
          }
        }
      `);
      return response.data.orders.edges.map(({ node }: any) => node);
    }
    
    // Mark an order as fulfilled
    export async function fulfillOrder(orderId: string, trackingNumber: string, trackingCompany: string) {
      // First get fulfillment order ID
      const orderResponse = await adminClient.request(`
        query GetFulfillmentOrders($id: ID!) {
          order(id: $id) {
            fulfillmentOrders(first: 5) {
              edges {
                node { id status lineItems(first: 20) { edges { node { id remainingQuantity } } } }
              }
            }
          }
        }
      `, { variables: { id: orderId } });
    
      const fulfillmentOrder = orderResponse.data.order.fulfillmentOrders.edges[0]?.node;
      if (!fulfillmentOrder) throw new Error("No fulfillment order found");
    
      const response = await adminClient.request(`
        mutation FulfillOrder($fulfillment: FulfillmentInput!) {
          fulfillmentCreate(fulfillment: $fulfillment) {
            fulfillment { id status }
            userErrors { field message }
          }
        }
      `, {
        variables: {
          fulfillment: {
            lineItemsByFulfillmentOrder: [{ fulfillmentOrderId: fulfillmentOrder.id }],
            trackingInfo: { number: trackingNumber, company: trackingCompany },
            notifyCustomer: true,
          },
        },
      });
      return response.data.fulfillmentCreate;
    }
    
  5. 大規模なデータセットに Bulk Operations を実行する

    数千件の商品や注文をエクスポートする場合は、Bulk Operations(GraphQL のみ)を使用してください。非同期で実行され、JSONL ファイルの URL が返されます。

    // Start a bulk operation
    export async function startBulkProductExport() {
      const response = await adminClient.request(`
        mutation {
          bulkOperationRunQuery(
            query: """
            {
              products {
                edges {
                  node {
                    id title status
                    variants {
                      edges {
                        node { id sku price inventoryQuantity }
                      }
                    }
                  }
                }
              }
            }
            """
          ) {
            bulkOperation { id status }
            userErrors { field message }
          }
        }
      `);
      return response.data.bulkOperationRunQuery.bulkOperation;
    }
    
    // Poll for completion and download URL
    export async function getBulkOperationStatus() {
      const response = await adminClient.request(`
        query {
          currentBulkOperation {
            id status errorCode
            objectCount
            url  # JSONL download URL — available when status is COMPLETED
          }
        }
      `);
      return response.data.currentBulkOperation;
    }
    

例

REST API による顧客の検索と更新
// REST is still valid for simple lookups where GraphQL overhead isn't worth it
export async function searchCustomers(email: string) {
  const response = await restClient.get({
    path: "customers/search",
    query: { query: `email:${email}` },
  });
  return response.body.customers;
}

export async function tagCustomer(customerId: number, tags: string[]) {
  const response = await restClient.put({
    path: `customers/${customerId}`,
    data: { customer: { id: customerId, tags: tags.join(",") } },
  });
  return response.body.customer;
}
在庫調整
export async function adjustInventory(inventoryItemId: string, locationId: string, delta: number) {
  const response = await adminClient.request(`
    mutation AdjustInventory($input: InventoryAdjustQuantitiesInput!) {
      inventoryAdjustQuantities(input: $input) {
        inventoryAdjustmentGroup {
          changes {
            name
            delta
            item { id }
            location { name }
          }
        }
        userErrors { field message }
      }
    }
  `, {
    variables: {
      input: {
        name: "available",
        reason: "correction",
        changes: [
          {
            inventoryItemId,
            locationId,
            delta,
          },
        ],
      },
    },
  });
  return response.data.inventoryAdjustQuantities;
}

ベストプラクティス

  • REST より GraphQL を優先する — GraphQL のコストベースのレート制限(毎秒 1000 コスト単位)は、REST の毎秒 40 リクエストより余裕があり、過剰なデータ取得も防げます
  • 250 件を超えるデータのエクスポートには Bulk Operations を使用する — 数千件のレコードを手動でページングしないでください。Bulk Operations なら、単一の非同期ジョブで数百万件まで処理できます
  • ミューテーションでは必ず userErrors を処理する — HTTP 200 のレスポンスが成功を意味するとは限りません。ミューテーションを成功として扱う前に、userErrors 配列を確認してください
  • ID には gid://shopify/Product/123 形式を使用する — Admin API GraphQL ではグローバル ID を使用します。GID プレフィックスなしで数値 ID を送信しないでください
  • 指数バックオフを実装する — Retry-After ヘッダーを尊重し、429 および 503 のレスポンスに対してバックオフを実装してください
  • 変更されない商品データをキャッシュする — 商品タイトルやハンドルはほとんど変更されません。API 呼び出しを減らすため、適切な TTL を設定してキャッシュしてください
  • 権限は必要最小限にする — 要求するスコープを減らすと、インストール時にマーチャントが感じる抵抗を軽減できます

よくある問題

問題解決策
GraphQL エラー Cost exceeds bucket size接続の first: 引数を小さくする(250 ではなく 50 を使用する)か、深くネストした接続を避けるようクエリを再構成します
数値 ID と GID 形式の不一致REST の数値 ID は必ず GID 形式に変換します:gid://shopify/Product/${numericId}
Bulk Operation の URL が 403 を返すJSONL の URL は有効期限付きの署名済み S3 URL です。COMPLETED ステータスを確認したら、直ちにダウンロードしてください
FULFILLMENT_ORDER_NOT_FOUND で注文のフルフィルメントに失敗するAPI バージョン 2022-07 以降、注文のフルフィルメントには従来の Fulfillments API ではなく Fulfillment Orders API を使用する必要があります
Webhook イベントによって重複処理が発生するWebhook ペイロードの admin_graphql_api_id を使用し、冪等性キーを実装してください
顧客の更新時に既存のタグが消去されるタグを更新する際は、必ず現在のタグを取得してから追加してください。API はタグを統合せず、置き換えます

関連スキル

  • @shopify-app-development
  • @shopify-webhooks
  • @shopify-metafields
  • @shopify-storefront-api
  • @bulk-data-operations

コメント